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 }