426 lines
11 KiB
Go
426 lines
11 KiB
Go
package service
|
|
|
|
import (
|
|
"fmt"
|
|
"path"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ssgo/discover"
|
|
"github.com/ssgo/s"
|
|
"github.com/ssgo/standard"
|
|
"github.com/ssgo/u"
|
|
)
|
|
|
|
type regexRedirectInfo struct {
|
|
To string
|
|
Regex regexp.Regexp
|
|
}
|
|
|
|
var _proxies = map[string]string{}
|
|
var _proxiesLock = sync.RWMutex{}
|
|
var _regexProxies = map[string]*regexRedirectInfo{}
|
|
var _rewrites = map[string]string{}
|
|
var _regexRewrites = map[string]*regexRedirectInfo{}
|
|
var _rewritesLock = sync.RWMutex{}
|
|
var _statics = map[string]string{}
|
|
var _staticsLock = sync.RWMutex{}
|
|
|
|
func UpdateStatic(in map[string]string) bool {
|
|
updated := false
|
|
for k, v := range in {
|
|
_staticsLock.RLock()
|
|
v1 := _statics[k]
|
|
_staticsLock.RUnlock()
|
|
if v == v1 {
|
|
continue
|
|
}
|
|
|
|
s.ServerLogger.Info(u.StringIf(v1 != "", "update static set", "new static set"), "key", k, "value", v)
|
|
_staticsLock.Lock()
|
|
_statics[k] = v
|
|
_staticsLock.Unlock()
|
|
a := strings.SplitN(k, "/", 2)
|
|
if len(a) == 1 {
|
|
a = append(a, "/")
|
|
}
|
|
if a[0] == "*" {
|
|
a[0] = ""
|
|
}
|
|
if a[1] == "" {
|
|
a[1] = "/"
|
|
}
|
|
s.StaticByHost(a[1], v, a[0])
|
|
updated = true
|
|
}
|
|
return updated
|
|
}
|
|
|
|
func UpdateProxy(in map[string]string) bool {
|
|
updated := false
|
|
//fmt.Println("####000")
|
|
|
|
for k, v := range in {
|
|
//fmt.Println("####111", k, v)
|
|
_proxiesLock.RLock()
|
|
v1 := _proxies[k]
|
|
v2 := _regexProxies[k]
|
|
_proxiesLock.RUnlock()
|
|
// skip same
|
|
if v == v1 {
|
|
//fmt.Println("####222", k, v)
|
|
continue
|
|
}
|
|
////fmt.Println("####333", k, v)
|
|
if v2 != nil && v == v2.To {
|
|
continue
|
|
}
|
|
////fmt.Println("####444", k, v)
|
|
if strings.Contains(k, "(") {
|
|
// for regexp
|
|
////fmt.Println("####555", k, v)
|
|
matcher, err := regexp.Compile("^" + k + "$")
|
|
if err != nil {
|
|
s.ServerLogger.Error("proxy regexp compile failed", "key", k, "value", v)
|
|
//log.Print("Proxy Error Compile ", err)
|
|
} else {
|
|
s.ServerLogger.Info(u.StringIf(v2 != nil, "update regexp proxy set", "new regexp proxy set"), "key", k, "value", v)
|
|
_proxiesLock.Lock()
|
|
_regexProxies[k] = ®exRedirectInfo{
|
|
To: v,
|
|
Regex: *matcher,
|
|
}
|
|
_proxiesLock.Unlock()
|
|
updated = true
|
|
}
|
|
} else {
|
|
// for simple
|
|
////fmt.Println("####666", k, v)
|
|
s.ServerLogger.Info(u.StringIf(v1 != "", "update proxy set", "new proxy set"), "key", k, "value", v)
|
|
_proxiesLock.Lock()
|
|
_proxies[k] = v
|
|
_proxiesLock.Unlock()
|
|
|
|
// add app to discover
|
|
////fmt.Println("########2", len((*proxies)))
|
|
if !strings.Contains(v, "://") {
|
|
if discover.Config.Calls[v] == "" {
|
|
callConfig := ""
|
|
if strings.ContainsRune(v, ':') {
|
|
// support call config in proxy value
|
|
a := strings.SplitN(v, ":", 2)
|
|
v = a[0]
|
|
callConfig = a[1]
|
|
} else {
|
|
callConfig = (time.Duration(s.Config.ReadHeaderTimeout) * time.Millisecond).String()
|
|
}
|
|
if discover.Config.Registry != "" {
|
|
if discover.AddExternalApp(v, callConfig) {
|
|
updated = true
|
|
}
|
|
}
|
|
} else {
|
|
updated = true
|
|
}
|
|
} else {
|
|
updated = true
|
|
}
|
|
}
|
|
}
|
|
//fmt.Println("####999")
|
|
return updated
|
|
}
|
|
|
|
func UpdateRewrite(in map[string]string) bool {
|
|
updated := false
|
|
for k, v := range in {
|
|
_rewritesLock.RLock()
|
|
v1 := _rewrites[k]
|
|
v2 := _regexRewrites[k]
|
|
_rewritesLock.RUnlock()
|
|
|
|
// skip same
|
|
if v == v1 {
|
|
continue
|
|
}
|
|
if v2 != nil && v == v2.To {
|
|
continue
|
|
}
|
|
|
|
if strings.Contains(k, "(") {
|
|
matcher, err := regexp.Compile("^" + k + "$")
|
|
if err != nil {
|
|
s.ServerLogger.Error("rewrite regexp compile failed", "key", k, "value", v)
|
|
} else {
|
|
s.ServerLogger.Info(u.StringIf(v2 != nil, "update regexp rewrite set", "new regexp rewrite set"), "key", k, "value", v)
|
|
_rewritesLock.Lock()
|
|
_regexRewrites[k] = ®exRedirectInfo{
|
|
To: v,
|
|
Regex: *matcher,
|
|
}
|
|
_rewritesLock.Unlock()
|
|
updated = true
|
|
}
|
|
} else {
|
|
_rewritesLock.Lock()
|
|
_rewrites[k] = v
|
|
_rewritesLock.Unlock()
|
|
updated = true
|
|
}
|
|
}
|
|
return updated
|
|
}
|
|
|
|
// TODO 测试各种情况下的 matchRedirect
|
|
func rewrite(request *s.Request) (toPath string, rewrite bool) {
|
|
if toApp, toPath, ok := matchRedirect(request, &_rewrites, &_regexRewrites, &_rewritesLock); ok {
|
|
return toApp + toPath, true
|
|
}
|
|
return
|
|
}
|
|
|
|
func proxy(request *s.Request) (authLevel int, toApp, toPath *string, headers map[string]string) {
|
|
if toApp1, toPath1, ok := matchRedirect(request, &_proxies, &_regexProxies, &_proxiesLock); ok {
|
|
outHeaders := map[string]string{
|
|
standard.DiscoverHeaderFromApp: "gateway",
|
|
standard.DiscoverHeaderFromNode: s.GetServerAddr(),
|
|
}
|
|
requestPath := request.RequestURI
|
|
if requestPath == "" {
|
|
requestPath = request.URL.Path
|
|
}
|
|
|
|
pos := strings.Index(requestPath, toPath1)
|
|
if pos > 0 {
|
|
outHeaders["Proxy-Path"] = requestPath[0:pos]
|
|
}
|
|
return 0, &toApp1, &toPath1, outHeaders
|
|
}
|
|
return
|
|
}
|
|
|
|
func ClearRewritesAndProxies() {
|
|
_staticsLock.Lock()
|
|
_statics = map[string]string{}
|
|
_staticsLock.Unlock()
|
|
_rewritesLock.Lock()
|
|
_rewrites = map[string]string{}
|
|
_regexRewrites = map[string]*regexRedirectInfo{}
|
|
_rewritesLock.Unlock()
|
|
_proxiesLock.Lock()
|
|
_proxies = map[string]string{}
|
|
_regexProxies = map[string]*regexRedirectInfo{}
|
|
_proxiesLock.Unlock()
|
|
}
|
|
|
|
func MatchRewrite(request *s.Request) (toApp, toPath string, ok bool) {
|
|
return matchRedirect(request, &_rewrites, &_regexRewrites, &_rewritesLock)
|
|
}
|
|
|
|
func MatchProxy(request *s.Request) (toApp, toPath string, ok bool) {
|
|
return matchRedirect(request, &_proxies, &_regexProxies, &_proxiesLock)
|
|
}
|
|
|
|
func matchRedirect(request *s.Request, normalList *map[string]string, regexpList *map[string]*regexRedirectInfo, lock *sync.RWMutex) (toApp, toPath string, ok bool) {
|
|
(*lock).RLock()
|
|
n1 := len(*normalList)
|
|
n2 := len(*regexpList)
|
|
(*lock).RUnlock()
|
|
|
|
if n1 == 0 && n2 == 0 {
|
|
return
|
|
}
|
|
|
|
host1 := ""
|
|
host2 := ""
|
|
if strings.ContainsRune(request.Host, ':') {
|
|
hostArr := strings.SplitN(request.Host, ":", 2)
|
|
host1 = hostArr[0]
|
|
host2 = request.Host
|
|
} else {
|
|
host1 = request.Host
|
|
host2 = request.Host + ":" + u.StringIf(request.URL.Scheme == "https", "443", "80")
|
|
}
|
|
|
|
requestPath := request.RequestURI
|
|
if requestPath == "" {
|
|
requestPath = request.URL.Path
|
|
}
|
|
pathMatchers := make([]string, 0)
|
|
pathMatchers = append(pathMatchers, fmt.Sprint(request.URL.Scheme, "://", host1, requestPath))
|
|
pathMatchers = append(pathMatchers, fmt.Sprint(request.URL.Scheme, "://", host2, requestPath))
|
|
pathMatchers = append(pathMatchers, fmt.Sprint(host1, requestPath))
|
|
pathMatchers = append(pathMatchers, fmt.Sprint(host2, requestPath))
|
|
pathMatchers = append(pathMatchers, requestPath)
|
|
|
|
hostMatchers := make([]string, 0)
|
|
hostMatchers = append(hostMatchers, fmt.Sprint(request.URL.Scheme, "://", host1))
|
|
hostMatchers = append(hostMatchers, fmt.Sprint(request.URL.Scheme, "://", host2))
|
|
hostMatchers = append(hostMatchers, host1)
|
|
hostMatchers = append(hostMatchers, host2)
|
|
|
|
if n1 > 0 {
|
|
list1 := map[string]string{}
|
|
(*lock).RLock()
|
|
for k, v := range *normalList {
|
|
list1[k] = v
|
|
}
|
|
(*lock).RUnlock()
|
|
for setKey, setValue := range list1 {
|
|
matchPath := ""
|
|
matchPathArr := strings.SplitN(strings.ReplaceAll(setKey, "://", ""), "/", 2)
|
|
if len(matchPathArr) == 2 {
|
|
matchPath = "/" + matchPathArr[1]
|
|
}
|
|
|
|
toApp, toPath = splitAppAndPath(setValue)
|
|
if matchPath == "" {
|
|
for _, matchStr := range hostMatchers {
|
|
if matchStr == setKey {
|
|
// fmt.Println(" >>>>>>>>1", setKey, matchStr, requestPath)
|
|
return toApp, path.Join(toPath, requestPath), true
|
|
}
|
|
}
|
|
} else {
|
|
for _, matchStr := range pathMatchers {
|
|
// fmt.Println(" >>>>>>>> test", u.BCyan(matchStr), u.BMagenta(setKey), "|", strings.HasPrefix(matchStr, setKey))
|
|
if strings.HasPrefix(matchStr, setKey) {
|
|
if strings.HasPrefix(requestPath, matchPath) {
|
|
p2 := requestPath[len(matchPath):]
|
|
if len(p2) == 0 || p2[0] != '/' {
|
|
p2 = "/" + p2
|
|
}
|
|
// fmt.Println(" >>>>>>>>2", setKey, matchStr, p2)
|
|
return toApp, path.Join(toPath, p2), true
|
|
} else {
|
|
// fmt.Println(" >>>>>>>>3", setKey, matchStr, requestPath)
|
|
return toApp, path.Join(toPath, requestPath), true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if n2 > 0 {
|
|
// 模糊匹配
|
|
list2 := map[string]*regexRedirectInfo{}
|
|
(*lock).RLock()
|
|
for k, v := range *regexpList {
|
|
list2[k] = v
|
|
}
|
|
(*lock).RUnlock()
|
|
|
|
// requestUrl := request.Host + requestPath
|
|
for setKey, setInfo := range list2 {
|
|
matchPath := ""
|
|
matchPathArr := strings.SplitN(strings.ReplaceAll(setKey, "://", ""), "/", 2)
|
|
if len(matchPathArr) == 2 {
|
|
matchPath = "/" + matchPathArr[1]
|
|
}
|
|
|
|
// fmt.Println(" >>>>>>>> matchPath", setKey, matchPath, u.JsonP(matchPathArr), 111)
|
|
var matchList []string
|
|
if matchPath == "" {
|
|
matchList = hostMatchers
|
|
} else {
|
|
matchList = pathMatchers
|
|
}
|
|
for _, matchStr := range matchList {
|
|
finds := setInfo.Regex.FindStringSubmatch(matchStr)
|
|
if len(finds) > 0 {
|
|
matchResult := setInfo.To
|
|
for i := 1; i < len(finds); i++ {
|
|
matchResult = strings.ReplaceAll(matchResult, fmt.Sprintf("$%d", i), finds[i])
|
|
}
|
|
// fmt.Println(" >>>>>>>> test", u.BCyan(matchStr), u.Cyan(matchPath), u.BMagenta(setKey), "|", matchResult, fixAppName(matchResult), "...")
|
|
toApp, toPath := splitAppAndPath(matchResult)
|
|
return toApp, toPath, true
|
|
}
|
|
}
|
|
|
|
// //fmt.Println("check regexp proxy ", rp.Regex, rp.Value)
|
|
// // finds := setInfo.Regex.FindAllStringSubmatch(requestUrl, 20)
|
|
// fmt.Println(" >>>>>>>> test", u.BCyan(matchStr), u.BMagenta(setKey), "|", strings.HasPrefix(matchStr, setKey))
|
|
// if len(finds) > 0 && len(finds[0]) > 2 {
|
|
// //fmt.Println(" >>>>>>>>2", requestPath, finds[0][2])
|
|
// // pos := strings.Index(requestPath, finds[0][2])
|
|
// // if pos > 0 {
|
|
// // outHeaders["Proxy-Path"] = requestPath[0:pos]
|
|
// // }
|
|
|
|
// if !strings.Contains(finds[0][1], "://") && strings.ContainsRune(finds[0][1], ':') {
|
|
// callConfig := ""
|
|
// if strings.ContainsRune(finds[0][1], ':') {
|
|
// // support call config in proxy value
|
|
// a := strings.SplitN(finds[0][1], ":", 2)
|
|
// finds[0][1] = a[0]
|
|
// callConfig = a[1]
|
|
// } else {
|
|
// callConfig = (time.Duration(s.Config.ReadHeaderTimeout) * time.Millisecond).String()
|
|
// }
|
|
// if discover.Config.Registry != "" {
|
|
// discover.AddExternalApp(finds[0][1], callConfig)
|
|
// }
|
|
// }
|
|
// return finds[0][1], finds[0][2], true
|
|
// }
|
|
}
|
|
}
|
|
|
|
// 不进行代理
|
|
return
|
|
}
|
|
|
|
// func fixAppName(appName string) string {
|
|
// if !strings.Contains(appName, "://") && strings.ContainsRune(appName, ':') {
|
|
// a := strings.SplitN(appName, "/", 2)
|
|
// return a[0]
|
|
// } else {
|
|
// return appName
|
|
// }
|
|
// }
|
|
|
|
func splitAppAndPath(to string) (toApp, toPath string) {
|
|
if strings.Contains(to, "://") {
|
|
to = strings.Replace(to, "://", ":--", 1)
|
|
a := strings.SplitN(to, "/", 2)
|
|
if len(a) == 1 {
|
|
a = append(a, "")
|
|
}
|
|
a[0] = strings.Replace(a[0], ":--", "://", 1)
|
|
return a[0], "/" + a[1]
|
|
} else {
|
|
if strings.HasPrefix(to, "/") {
|
|
toApp = ""
|
|
toPath = makeAppConfig(to)
|
|
} else {
|
|
a := strings.SplitN(to, "/", 2)
|
|
if len(a) == 1 {
|
|
a = append(a, "")
|
|
}
|
|
toApp = makeAppConfig(a[0])
|
|
toPath = "/" + a[1]
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func makeAppConfig(toStr string) (toApp string) {
|
|
toAppConfig := ""
|
|
if strings.ContainsRune(toApp, ':') {
|
|
a := strings.SplitN(toApp, ":", 2)
|
|
toApp = a[0]
|
|
toAppConfig = a[1]
|
|
} else {
|
|
toApp = toStr
|
|
// toAppConfig = (time.Duration(s.Config.RedirectTimeout) * time.Millisecond).String()
|
|
}
|
|
if discover.Config.Registry != "" {
|
|
discover.AddExternalApp(toApp, toAppConfig)
|
|
}
|
|
return toApp
|
|
}
|