func GetURLFromService(spec *APISpec) (*tykcommon.HostList, error) { doCacheRefresh := func() (*tykcommon.HostList, error) { log.Debug("--> Refreshing") spec.ServiceRefreshInProgress = true sd := ServiceDiscovery{} sd.New(&spec.Proxy.ServiceDiscovery) data, err := sd.GetTarget(spec.Proxy.ServiceDiscovery.QueryEndpoint) if err == nil { // Set the cached value if data.Len() == 0 { spec.HasRun = true spec.ServiceRefreshInProgress = false log.Warning("[PROXY][SD] Service Discovery returned empty host list! Returning last good set.") if spec.LastGoodHostList == nil { log.Warning("[PROXY][SD] Last good host list is nil, returning empty set.") spec.LastGoodHostList = tykcommon.NewHostList() } return spec.LastGoodHostList, nil } ServiceCache.Set(spec.APIID, data, cache.DefaultExpiration) // Stash it too spec.LastGoodHostList = data spec.HasRun = true spec.ServiceRefreshInProgress = false return data, err } spec.ServiceRefreshInProgress = false return nil, err } // First time? Refresh the cache and return that if !spec.HasRun { log.Debug("First run! Setting cache") return doCacheRefresh() } // Not first run - check the cache cachedServiceData, found := ServiceCache.Get(spec.APIID) if !found { if spec.ServiceRefreshInProgress { // Are we already refreshing the cache? skip and return last good conf log.Debug("Cache expired! But service refresh in progress") return spec.LastGoodHostList, nil } // Refresh the spec log.Debug("Cache expired! Refreshing...") return doCacheRefresh() } log.Debug("Returning from cache.") return cachedServiceData.(*tykcommon.HostList), nil }
func (s *ServiceDiscovery) ProcessRawData(rawData string) (*tykcommon.HostList, error) { var jsonParsed gabs.Container hostlist := tykcommon.NewHostList() if s.endpointReturnsList { // Convert to an object s.ConvertRawListToObj(&rawData) if err := s.ParseObject(rawData, &jsonParsed); err != nil { log.Error("Parse object failed: ", err) return nil, err } log.Debug("Parsed object list: ", jsonParsed) // Treat JSON as a list and then apply the data path if s.isTargetList { // Get all values asList := s.GetSubObjectFromList(&jsonParsed) log.Debug("Host list:", asList) hostlist.Set(*asList) return hostlist, nil } // Get the top value list := s.GetSubObjectFromList(&jsonParsed) var host string for _, v := range *list { host = v break } hostlist.Set([]string{host}) return hostlist, nil } // It's an object s.ParseObject(rawData, &jsonParsed) if s.isTargetList { // It's a list object log.Debug("It's a target list - getting sub object from list") log.Debug("Passing in: ", jsonParsed) asList := s.GetSubObjectFromList(&jsonParsed) hostlist.Set(*asList) log.Debug("Got from object: ", hostlist) return hostlist, nil } // It's a single object host := s.GetSubObject(&jsonParsed) hostlist.Set([]string{host}) return hostlist, nil }
// TykNewSingleHostReverseProxy returns a new ReverseProxy that rewrites // URLs to the scheme, host, and base path provided in target. If the // target's path is "/base" and the incoming request was for "/dir", // the target request will be for /base/dir. This version modifies the // stdlib version by also setting the host to the target, this allows // us to work with heroku and other such providers func TykNewSingleHostReverseProxy(target *url.URL, spec *APISpec) *ReverseProxy { // initalise round robin spec.RoundRobin = &RoundRobin{} spec.RoundRobin.SetMax(tykcommon.NewHostList()) if spec.Proxy.ServiceDiscovery.UseDiscoveryService { log.Debug("[PROXY] Service discovery enabled") if ServiceCache == nil { log.Debug("[PROXY] Service cache initialising") expiry := 120 if config.ServiceDiscovery.DefaultCacheTimeout > 0 { expiry = config.ServiceDiscovery.DefaultCacheTimeout } ServiceCache = cache.New(time.Duration(expiry)*time.Second, 15*time.Second) } } targetQuery := target.RawQuery director := func(req *http.Request) { var targetSet bool if spec.Proxy.ServiceDiscovery.UseDiscoveryService { tempTargetURL, tErr := GetURLFromService(spec) if tErr != nil { log.Error("[PROXY] [SERVICE DISCOVERY] Failed target lookup: ", tErr) } else { // No error, replace the target if spec.Proxy.EnableLoadBalancing { remote, err := url.Parse(GetNextTarget(tempTargetURL, spec, 0)) if err != nil { log.Error("[PROXY] [SERVICE DISCOVERY] Couldn't parse target URL:", err) } else { // Only replace target if everything is OK target = remote targetQuery = target.RawQuery } } else { remote, err := url.Parse(GetNextTarget(tempTargetURL, spec, 0)) if err != nil { log.Error("[PROXY] [SERVICE DISCOVERY] Couldn't parse target URL:", err) } else { // Only replace target if everything is OK target = remote targetQuery = target.RawQuery } } } // We've overriden remote now, don;t need to do it again targetSet = true } if !targetSet { // no override, better check if LB is enabled if spec.Proxy.EnableLoadBalancing { // it is, lets get that target data lbRemote, lbErr := url.Parse(GetNextTarget(spec.Proxy.StructuredTargetList, spec, 0)) if lbErr != nil { log.Error("[PROXY] [LOAD BALANCING] Couldn't parse target URL:", lbErr) } else { // Only replace target if everything is OK target = lbRemote targetQuery = target.RawQuery } } } // Specifically override with a URL rewrite var newTarget *url.URL switchTargets := false if spec.URLRewriteEnabled { URLRewriteContainsTarget, found := context.GetOk(req, RetainHost) if found { if URLRewriteContainsTarget.(bool) { log.Debug("Detected host rewrite, overriding target") tmpTarget, pErr := url.Parse(req.URL.String()) if pErr != nil { log.Error("Failed to parse URL! Err: ", pErr) } else { newTarget = tmpTarget switchTargets = true } context.Clear(req) } } } // No override, and no load balancing? Use the existing target targetToUse := target if switchTargets { targetToUse = newTarget } req.URL.Scheme = targetToUse.Scheme req.URL.Host = targetToUse.Host req.URL.Path = singleJoiningSlash(targetToUse.Path, req.URL.Path) if !spec.Proxy.PreserveHostHeader { req.Host = targetToUse.Host } if targetQuery == "" || req.URL.RawQuery == "" { req.URL.RawQuery = targetQuery + req.URL.RawQuery } else { req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery } } return &ReverseProxy{Director: director, TykAPISpec: spec, FlushInterval: time.Duration(config.HttpServerOptions.FlushInterval) * time.Millisecond} }