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
}
Esempio n. 2
0
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}
}