2026-05-08 07:27:06 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"apigo.cc/go/log"
|
|
|
|
|
"fmt"
|
|
|
|
|
"net/url"
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
)
|
|
|
|
|
|
2026-05-09 16:39:20 +08:00
|
|
|
type rewriteType struct {
|
2026-05-13 00:43:43 +08:00
|
|
|
matcher *regexp.Regexp
|
|
|
|
|
fromPath string
|
|
|
|
|
toPath string
|
|
|
|
|
hasWildcard bool
|
|
|
|
|
prefix string
|
|
|
|
|
toPrefix string
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-13 00:43:43 +08:00
|
|
|
func parseRewriteRule(fromPath, toPath string) *rewriteType {
|
|
|
|
|
s := &rewriteType{fromPath: fromPath, toPath: toPath}
|
|
|
|
|
if strings.ContainsRune(fromPath, '(') {
|
|
|
|
|
matcher, err := regexp.Compile("^" + fromPath + "$")
|
2026-05-08 07:27:06 +08:00
|
|
|
if err == nil {
|
|
|
|
|
s.matcher = matcher
|
|
|
|
|
}
|
2026-05-13 00:43:43 +08:00
|
|
|
} else if strings.HasSuffix(fromPath, "/*") {
|
|
|
|
|
s.hasWildcard = true
|
|
|
|
|
s.prefix = fromPath[:len(fromPath)-1]
|
|
|
|
|
if strings.HasSuffix(toPath, "/*") {
|
|
|
|
|
s.toPrefix = toPath[:len(toPath)-1]
|
|
|
|
|
} else {
|
|
|
|
|
s.toPrefix = toPath
|
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
2026-05-13 00:43:43 +08:00
|
|
|
return s
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (hc *HostContext) Rewrite(path string, toPath string) *HostContext {
|
|
|
|
|
s := parseRewriteRule(path, toPath)
|
2026-05-08 07:27:06 +08:00
|
|
|
|
2026-05-09 16:39:20 +08:00
|
|
|
hostPoliciesLock.Lock()
|
|
|
|
|
defer hostPoliciesLock.Unlock()
|
2026-05-12 23:53:16 +08:00
|
|
|
codeRewrites[hc.host] = append(codeRewrites[hc.host], s)
|
|
|
|
|
rebuildRewritesUnderLock(hc.host)
|
2026-05-09 16:39:20 +08:00
|
|
|
return hc
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:53:16 +08:00
|
|
|
func rebuildRewritesUnderLock(host string) {
|
|
|
|
|
var combined []*rewriteType
|
|
|
|
|
combined = append(combined, codeRewrites[host]...)
|
|
|
|
|
combined = append(combined, fileRewrites[host]...)
|
|
|
|
|
combined = append(combined, dynamicRewrites[host]...)
|
|
|
|
|
hostRewrites[host] = combined
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 07:27:06 +08:00
|
|
|
func processRewrite(request *Request, response *Response, logger *log.Logger) bool {
|
2026-05-09 16:39:20 +08:00
|
|
|
host := request.Host
|
|
|
|
|
hostOnly, port, _ := strings.Cut(host, ":")
|
|
|
|
|
hosts := []string{host}
|
|
|
|
|
if port != "" {
|
|
|
|
|
hosts = append(hosts, hostOnly, ":"+port)
|
|
|
|
|
}
|
|
|
|
|
hosts = append(hosts, "*")
|
|
|
|
|
|
|
|
|
|
hostPoliciesLock.RLock()
|
|
|
|
|
defer hostPoliciesLock.RUnlock()
|
|
|
|
|
|
2026-05-08 07:27:06 +08:00
|
|
|
requestPath := request.RequestURI
|
|
|
|
|
queryString := ""
|
|
|
|
|
if pos := strings.Index(requestPath, "?"); pos != -1 {
|
|
|
|
|
queryString = requestPath[pos:]
|
|
|
|
|
requestPath = requestPath[:pos]
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-09 16:39:20 +08:00
|
|
|
for _, h := range hosts {
|
|
|
|
|
rewrites, exists := hostRewrites[h]
|
|
|
|
|
if !exists {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
|
2026-05-09 16:39:20 +08:00
|
|
|
for _, ri := range rewrites {
|
|
|
|
|
found := false
|
|
|
|
|
rewriteToPath := ""
|
2026-05-08 07:27:06 +08:00
|
|
|
|
2026-05-13 00:43:43 +08:00
|
|
|
if ri.matcher != nil {
|
2026-05-08 07:27:06 +08:00
|
|
|
finds := ri.matcher.FindAllStringSubmatch(request.RequestURI, 1)
|
|
|
|
|
if len(finds) > 0 {
|
|
|
|
|
toPath := ri.toPath
|
|
|
|
|
for i, part := range finds[0] {
|
|
|
|
|
toPath = strings.ReplaceAll(toPath, fmt.Sprintf("$%d", i), part)
|
|
|
|
|
}
|
|
|
|
|
rewriteToPath = toPath
|
|
|
|
|
found = true
|
|
|
|
|
}
|
2026-05-13 00:43:43 +08:00
|
|
|
} else if ri.hasWildcard {
|
|
|
|
|
if strings.HasPrefix(requestPath, ri.prefix) {
|
|
|
|
|
suffix := requestPath[len(ri.prefix):]
|
|
|
|
|
rewriteToPath = ri.toPrefix + suffix
|
|
|
|
|
found = true
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if ri.fromPath == requestPath {
|
|
|
|
|
rewriteToPath = ri.toPath
|
|
|
|
|
found = true
|
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 16:39:20 +08:00
|
|
|
if found {
|
|
|
|
|
if strings.Contains(rewriteToPath, "://") {
|
|
|
|
|
// 外部重定向
|
|
|
|
|
if !strings.Contains(rewriteToPath, "?") && queryString != "" {
|
|
|
|
|
rewriteToPath += queryString
|
|
|
|
|
}
|
|
|
|
|
response.Header().Set("Location", rewriteToPath)
|
|
|
|
|
response.WriteHeader(302)
|
|
|
|
|
return true
|
|
|
|
|
} else {
|
|
|
|
|
// 内部重写
|
|
|
|
|
logger.Info("rewrite", "from", request.RequestURI, "to", rewriteToPath, "host", h)
|
|
|
|
|
if queryString != "" && !strings.Contains(rewriteToPath, "?") {
|
|
|
|
|
rewriteToPath += queryString
|
|
|
|
|
}
|
|
|
|
|
request.RequestURI = rewriteToPath
|
|
|
|
|
request.URL, _ = url.Parse(rewriteToPath)
|
|
|
|
|
return false // 继续后续处理
|
|
|
|
|
}
|
2026-05-08 07:27:06 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false
|
|
|
|
|
}
|
2026-05-12 23:18:31 +08:00
|
|
|
|
|
|
|
|
// RewriteRule 定义了外部传递的 URL 重写规则
|
|
|
|
|
type RewriteRule struct {
|
|
|
|
|
Path string // 原始路径或匹配正则,例如 ^/old/(.*)$
|
|
|
|
|
ToPath string // 重写后的路径,例如 /new/$1
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-12 23:53:16 +08:00
|
|
|
// ReplaceRewrites 使用 Copy-on-Write 机制原子地替换指定 host 下的动态重写规则。
|
2026-05-12 23:18:31 +08:00
|
|
|
func ReplaceRewrites(host string, rules []RewriteRule) {
|
|
|
|
|
newRewrites := make([]*rewriteType, 0, len(rules))
|
|
|
|
|
for _, r := range rules {
|
2026-05-13 00:43:43 +08:00
|
|
|
newRewrites = append(newRewrites, parseRewriteRule(r.Path, r.ToPath))
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hostPoliciesLock.Lock()
|
|
|
|
|
defer hostPoliciesLock.Unlock()
|
2026-05-12 23:53:16 +08:00
|
|
|
dynamicRewrites[host] = newRewrites
|
|
|
|
|
rebuildRewritesUnderLock(host)
|
2026-05-12 23:18:31 +08:00
|
|
|
}
|