feat: unified 'To' field for Proxy/Rewrite, support KV map configs, and auto-detect HTTP protocol for simple ports (by AI)
This commit is contained in:
parent
1bf819281a
commit
8a2f76ffc9
53
config.go
53
config.go
@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"apigo.cc/go/cast"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
@ -66,8 +67,8 @@ type ServiceConfig struct {
|
||||
StopTimeout int // 停止服务的超时时间 (ms)
|
||||
|
||||
// 从配置文件中加载的静态路由策略 (按 Host 分组,全局配置用 "" 或 "*")
|
||||
Proxies map[string][]ProxyRule
|
||||
Rewrites map[string][]RewriteRule
|
||||
Proxies map[string]map[string]any
|
||||
Rewrites map[string]map[string]any
|
||||
Statics map[string]map[string]string
|
||||
}
|
||||
|
||||
@ -78,31 +79,53 @@ func ApplyConfig() {
|
||||
hostPoliciesLock.Lock()
|
||||
defer hostPoliciesLock.Unlock()
|
||||
|
||||
// 清理旧的 file 策略
|
||||
// 1. Proxies KV 解析
|
||||
fileProxies = make(map[string][]*proxyType)
|
||||
fileRewrites = make(map[string][]*rewriteType)
|
||||
|
||||
for host, rules := range Config.Proxies {
|
||||
for host, kv := range Config.Proxies {
|
||||
if host == "*" {
|
||||
host = ""
|
||||
}
|
||||
newProxies := make([]*proxyType, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath))
|
||||
rules := make([]*proxyType, 0, len(kv))
|
||||
for path, val := range kv {
|
||||
if to, ok := val.(string); ok {
|
||||
// 极简 KV 模式: "/api/*": "user-svc/v1/*"
|
||||
rules = append(rules, parseProxyRule(0, path, "", "", to))
|
||||
} else {
|
||||
// 对象模式: "/api/*": {"To": "...", "Auth": 1}
|
||||
m, _ := cast.ToMap[string, any](val)
|
||||
rules = append(rules, parseProxyRule(
|
||||
cast.Int(m["Auth"]),
|
||||
path,
|
||||
cast.String(m["ToApp"]),
|
||||
cast.String(m["ToPath"]),
|
||||
cast.String(m["To"]),
|
||||
))
|
||||
}
|
||||
}
|
||||
fileProxies[host] = newProxies
|
||||
fileProxies[host] = rules
|
||||
rebuildProxiesUnderLock(host)
|
||||
}
|
||||
|
||||
for host, rules := range Config.Rewrites {
|
||||
// 2. Rewrites KV 解析
|
||||
fileRewrites = make(map[string][]*rewriteType)
|
||||
for host, kv := range Config.Rewrites {
|
||||
if host == "*" {
|
||||
host = ""
|
||||
}
|
||||
newRewrites := make([]*rewriteType, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath))
|
||||
rules := make([]*rewriteType, 0, len(kv))
|
||||
for path, val := range kv {
|
||||
if to, ok := val.(string); ok {
|
||||
rules = append(rules, parseRewriteRule(path, "", to))
|
||||
} else {
|
||||
m, _ := cast.ToMap[string, any](val)
|
||||
rules = append(rules, parseRewriteRule(
|
||||
path,
|
||||
cast.String(m["ToPath"]),
|
||||
cast.String(m["To"]),
|
||||
))
|
||||
}
|
||||
}
|
||||
fileRewrites[host] = newRewrites
|
||||
fileRewrites[host] = rules
|
||||
rebuildRewritesUnderLock(host)
|
||||
}
|
||||
|
||||
|
||||
48
proxy.go
48
proxy.go
@ -22,7 +22,40 @@ type proxyType struct {
|
||||
toPrefix string
|
||||
}
|
||||
|
||||
func parseProxyRule(authLevel int, path, toApp, toPath string) *proxyType {
|
||||
func parseTo(to string) (app, path string) {
|
||||
if to == "" {
|
||||
return "", "/"
|
||||
}
|
||||
|
||||
// 查找协议
|
||||
if strings.Contains(to, "://") {
|
||||
protocolPart, rest, _ := strings.Cut(to, "://")
|
||||
// 协议后的第一个 /
|
||||
if firstSlash := strings.Index(rest, "/"); firstSlash != -1 {
|
||||
app = protocolPart + "://" + rest[:firstSlash]
|
||||
path = rest[firstSlash:]
|
||||
} else {
|
||||
app = to
|
||||
path = "/"
|
||||
}
|
||||
} else {
|
||||
// 无协议,第一个 / 之前是 app
|
||||
if firstSlash := strings.Index(to, "/"); firstSlash != -1 {
|
||||
app = to[:firstSlash]
|
||||
path = to[firstSlash:]
|
||||
} else {
|
||||
app = to
|
||||
path = "/"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func parseProxyRule(authLevel int, path, toApp, toPath string, to string) *proxyType {
|
||||
if to != "" {
|
||||
toApp, toPath = parseTo(to)
|
||||
}
|
||||
|
||||
p := &proxyType{authLevel: authLevel, fromPath: path, toApp: toApp, toPath: toPath}
|
||||
if strings.ContainsRune(path, '(') {
|
||||
matcher, err := regexp.Compile("^" + path + "$")
|
||||
@ -41,8 +74,8 @@ func parseProxyRule(authLevel int, path, toApp, toPath string) *proxyType {
|
||||
return p
|
||||
}
|
||||
|
||||
func (hc *HostContext) Proxy(authLevel int, path string, toApp, toPath string) *HostContext {
|
||||
p := parseProxyRule(authLevel, path, toApp, toPath)
|
||||
func (hc *HostContext) Proxy(authLevel int, path string, to string) *HostContext {
|
||||
p := parseProxyRule(authLevel, path, "", "", to)
|
||||
|
||||
hostPoliciesLock.Lock()
|
||||
defer hostPoliciesLock.Unlock()
|
||||
@ -199,15 +232,16 @@ func copyResponse(res *gohttp.Result, response *Response, logger *log.Logger) {
|
||||
type ProxyRule struct {
|
||||
Path string // 匹配路径或正则,支持变量捕获如 ^/api/(.*)$
|
||||
AuthLevel int // 所需鉴权级别
|
||||
ToApp string // 目标 AppName 或完整 URL (可含 $1 变量替换)
|
||||
ToPath string // 目标路径 (可含 $1 变量替换)
|
||||
To string // 目标地址,格式为 "app/path" 或 "http://url/path" (支持后缀 /* 映射)
|
||||
ToApp string // [Deprecated] 目标 AppName 或完整 URL (可含 $1 变量替换)
|
||||
ToPath string // [Deprecated] 目标路径 (可含 $1 变量替换)
|
||||
}
|
||||
|
||||
// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的动态代理规则,不影响通过代码或文件写入的固定规则。
|
||||
// ReplaceProxies 使用全量指针替换的方式 (Copy-on-Write) 无缝更新指定 host 的动态代理规则。
|
||||
func ReplaceProxies(host string, rules []ProxyRule) {
|
||||
newProxies := make([]*proxyType, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath))
|
||||
newProxies = append(newProxies, parseProxyRule(r.AuthLevel, r.Path, r.ToApp, r.ToPath, r.To))
|
||||
}
|
||||
|
||||
hostPoliciesLock.Lock()
|
||||
|
||||
@ -43,7 +43,7 @@ func TestProxyDirect(t *testing.T) {
|
||||
defer backend.Close()
|
||||
|
||||
// 注册代理规则
|
||||
Host("*").Proxy(0, "/proxy", backend.URL, "/hello")
|
||||
Host("*").Proxy(0, "/proxy", backend.URL+"/hello")
|
||||
|
||||
rh := &RouteHandler{}
|
||||
req := httptest.NewRequest("GET", "/proxy", nil)
|
||||
|
||||
14
rewrite.go
14
rewrite.go
@ -17,7 +17,10 @@ type rewriteType struct {
|
||||
toPrefix string
|
||||
}
|
||||
|
||||
func parseRewriteRule(fromPath, toPath string) *rewriteType {
|
||||
func parseRewriteRule(fromPath, toPath, to string) *rewriteType {
|
||||
if to != "" {
|
||||
toPath = to
|
||||
}
|
||||
s := &rewriteType{fromPath: fromPath, toPath: toPath}
|
||||
if strings.ContainsRune(fromPath, '(') {
|
||||
matcher, err := regexp.Compile("^" + fromPath + "$")
|
||||
@ -36,8 +39,8 @@ func parseRewriteRule(fromPath, toPath string) *rewriteType {
|
||||
return s
|
||||
}
|
||||
|
||||
func (hc *HostContext) Rewrite(path string, toPath string) *HostContext {
|
||||
s := parseRewriteRule(path, toPath)
|
||||
func (hc *HostContext) Rewrite(path string, to string) *HostContext {
|
||||
s := parseRewriteRule(path, "", to)
|
||||
|
||||
hostPoliciesLock.Lock()
|
||||
defer hostPoliciesLock.Unlock()
|
||||
@ -135,14 +138,15 @@ func processRewrite(request *Request, response *Response, logger *log.Logger) bo
|
||||
// RewriteRule 定义了外部传递的 URL 重写规则
|
||||
type RewriteRule struct {
|
||||
Path string // 原始路径或匹配正则,例如 ^/old/(.*)$
|
||||
ToPath string // 重写后的路径,例如 /new/$1
|
||||
To string // 目标路径或完整 URL,例如 /new/$1
|
||||
ToPath string // [Deprecated] 重写后的路径
|
||||
}
|
||||
|
||||
// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的动态重写规则。
|
||||
func ReplaceRewrites(host string, rules []RewriteRule) {
|
||||
newRewrites := make([]*rewriteType, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath))
|
||||
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath, r.To))
|
||||
}
|
||||
|
||||
hostPoliciesLock.Lock()
|
||||
|
||||
@ -55,14 +55,18 @@ func (ws *WebServer) Start(ctx context.Context, logger *log.Logger) error {
|
||||
part := strings.Split(listenStr, "|")[0]
|
||||
addr, opts, _ := strings.Cut(part, ",")
|
||||
|
||||
protocol := "http"
|
||||
protocol := ""
|
||||
for _, opt := range strings.Split(opts, ",") {
|
||||
opt = strings.ToLower(strings.TrimSpace(opt))
|
||||
if opt == "h2c" || opt == "h2" {
|
||||
if opt == "h2c" || opt == "h2" || opt == "http" || opt == "https" {
|
||||
protocol = opt
|
||||
}
|
||||
}
|
||||
|
||||
if protocol == "" {
|
||||
protocol = "http" // Default to http
|
||||
}
|
||||
|
||||
if !strings.Contains(addr, ":") {
|
||||
addr = ":" + addr
|
||||
}
|
||||
|
||||
10
service.go
10
service.go
@ -260,16 +260,14 @@ func (gc *GroupContext) WebSocket(path string, serviceFunc any) *websocketServic
|
||||
return gc.hc.WebSocket(gc.prefix+path, serviceFunc)
|
||||
}
|
||||
|
||||
func (gc *GroupContext) Rewrite(path string, toPath string) *GroupContext {
|
||||
gc.hc.Rewrite(gc.prefix+path, toPath)
|
||||
func (gc *GroupContext) Rewrite(path string, to string) *GroupContext {
|
||||
gc.hc.Rewrite(gc.prefix+path, to)
|
||||
return gc
|
||||
}
|
||||
|
||||
func (gc *GroupContext) Proxy(authLevel int, path string, toApp, toPath string) *GroupContext {
|
||||
gc.hc.Proxy(authLevel, gc.prefix+path, toApp, toPath)
|
||||
func (gc *GroupContext) Proxy(authLevel int, path string, to string) *GroupContext {
|
||||
gc.hc.Proxy(authLevel, gc.prefix+path, to)
|
||||
return gc
|
||||
}
|
||||
|
||||
func (hc *HostContext) WebSocket(path string, serviceFunc any) *websocketServiceType {
|
||||
funcType := reflect.TypeOf(serviceFunc)
|
||||
if funcType.Kind() != reflect.Func {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user