github.com/craicoverflow/tyk@v2.9.6-rc3+incompatible/gateway/api_loader.go (about)

     1  package gateway
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"net/url"
     7  	"path/filepath"
     8  	"sort"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  
    13  	"github.com/gorilla/mux"
    14  	"github.com/justinas/alice"
    15  	"github.com/sirupsen/logrus"
    16  
    17  	"github.com/TykTechnologies/tyk/apidef"
    18  	"github.com/TykTechnologies/tyk/config"
    19  	"github.com/TykTechnologies/tyk/coprocess"
    20  	"github.com/TykTechnologies/tyk/storage"
    21  	"github.com/TykTechnologies/tyk/trace"
    22  )
    23  
    24  type ChainObject struct {
    25  	Domain         string
    26  	ListenOn       string
    27  	ThisHandler    http.Handler
    28  	RateLimitChain http.Handler
    29  	RateLimitPath  string
    30  	Open           bool
    31  	Index          int
    32  	Skip           bool
    33  	Subrouter      *mux.Router
    34  }
    35  
    36  func prepareStorage() generalStores {
    37  	var gs generalStores
    38  	gs.redisStore = &storage.RedisCluster{KeyPrefix: "apikey-", HashKeys: config.Global().HashKeys}
    39  	gs.redisOrgStore = &storage.RedisCluster{KeyPrefix: "orgkey."}
    40  	gs.healthStore = &storage.RedisCluster{KeyPrefix: "apihealth."}
    41  	gs.rpcAuthStore = &RPCStorageHandler{KeyPrefix: "apikey-", HashKeys: config.Global().HashKeys}
    42  	gs.rpcOrgStore = &RPCStorageHandler{KeyPrefix: "orgkey."}
    43  	FallbackKeySesionManager.Init(gs.redisStore)
    44  	return gs
    45  }
    46  
    47  func skipSpecBecauseInvalid(spec *APISpec, logger *logrus.Entry) bool {
    48  
    49  	switch spec.Protocol {
    50  	case "", "http", "https":
    51  		if spec.Proxy.ListenPath == "" {
    52  			logger.Error("Listen path is empty")
    53  			return true
    54  		}
    55  		if strings.Contains(spec.Proxy.ListenPath, " ") {
    56  			logger.Error("Listen path contains spaces, is invalid")
    57  			return true
    58  		}
    59  	}
    60  
    61  	_, err := url.Parse(spec.Proxy.TargetURL)
    62  	if err != nil {
    63  		logger.Error("couldn't parse target URL: ", err)
    64  		return true
    65  	}
    66  
    67  	return false
    68  }
    69  
    70  func generateDomainPath(hostname, listenPath string) string {
    71  	return hostname + listenPath
    72  }
    73  
    74  func countApisByListenHash(specs []*APISpec) map[string]int {
    75  	count := make(map[string]int, len(specs))
    76  	// We must track the hostname no matter what
    77  	for _, spec := range specs {
    78  		domainHash := generateDomainPath(spec.Domain, spec.Proxy.ListenPath)
    79  		if count[domainHash] == 0 {
    80  			dN := spec.Domain
    81  			if dN == "" {
    82  				dN = "(no host)"
    83  			}
    84  			mainLog.WithFields(logrus.Fields{
    85  				"api_name": spec.Name,
    86  				"domain":   dN,
    87  			}).Info("Tracking hostname")
    88  		}
    89  		count[domainHash]++
    90  	}
    91  	return count
    92  }
    93  
    94  func fixFuncPath(pathPrefix string, funcs []apidef.MiddlewareDefinition) {
    95  	for index := range funcs {
    96  		funcs[index].Path = filepath.Join(pathPrefix, funcs[index].Path)
    97  	}
    98  }
    99  
   100  func processSpec(spec *APISpec, apisByListen map[string]int,
   101  	gs *generalStores, subrouter *mux.Router, logger *logrus.Entry) *ChainObject {
   102  
   103  	var chainDef ChainObject
   104  	chainDef.Subrouter = subrouter
   105  
   106  	logger = logger.WithFields(logrus.Fields{
   107  		"org_id":   spec.OrgID,
   108  		"api_id":   spec.APIID,
   109  		"api_name": spec.Name,
   110  	})
   111  
   112  	var coprocessLog = logger.WithFields(logrus.Fields{
   113  		"prefix": "coprocess",
   114  	})
   115  
   116  	if len(spec.TagHeaders) > 0 {
   117  		// Ensure all headers marked for tagging are lowercase
   118  		lowerCaseHeaders := make([]string, len(spec.TagHeaders))
   119  		for i, k := range spec.TagHeaders {
   120  			lowerCaseHeaders[i] = strings.ToLower(k)
   121  
   122  		}
   123  		spec.TagHeaders = lowerCaseHeaders
   124  	}
   125  
   126  	if skipSpecBecauseInvalid(spec, logger) {
   127  		logger.Warning("Spec not valid, skipped!")
   128  		chainDef.Skip = true
   129  		return &chainDef
   130  	}
   131  
   132  	// Expose API only to looping
   133  	if spec.Internal {
   134  		chainDef.Skip = true
   135  	}
   136  
   137  	pathModified := false
   138  	for {
   139  		hash := generateDomainPath(spec.Domain, spec.Proxy.ListenPath)
   140  		if apisByListen[hash] < 2 {
   141  			// not a duplicate
   142  			break
   143  		}
   144  		if !pathModified {
   145  			prev := getApiSpec(spec.APIID)
   146  			if prev != nil && prev.Proxy.ListenPath == spec.Proxy.ListenPath {
   147  				// if this APIID was already loaded and
   148  				// had this listen path, let it keep it.
   149  				break
   150  			}
   151  			spec.Proxy.ListenPath += "-" + spec.APIID
   152  			pathModified = true
   153  		} else {
   154  			// keep adding '_' chars
   155  			spec.Proxy.ListenPath += "_"
   156  		}
   157  	}
   158  	if pathModified {
   159  		logger.Error("Listen path collision, changed to ", spec.Proxy.ListenPath)
   160  	}
   161  
   162  	// Set up LB targets:
   163  	if spec.Proxy.EnableLoadBalancing {
   164  		sl := apidef.NewHostListFromList(spec.Proxy.Targets)
   165  		spec.Proxy.StructuredTargetList = sl
   166  	}
   167  
   168  	// Initialise the auth and session managers (use Redis for now)
   169  	authStore := gs.redisStore
   170  	orgStore := gs.redisOrgStore
   171  	switch spec.AuthProvider.StorageEngine {
   172  	case LDAPStorageEngine:
   173  		storageEngine := LDAPStorageHandler{}
   174  		storageEngine.LoadConfFromMeta(spec.AuthProvider.Meta)
   175  		authStore = &storageEngine
   176  	case RPCStorageEngine:
   177  		authStore = gs.rpcAuthStore
   178  		orgStore = gs.rpcOrgStore
   179  		spec.GlobalConfig.EnforceOrgDataAge = true
   180  		globalConf := config.Global()
   181  		globalConf.EnforceOrgDataAge = true
   182  		config.SetGlobal(globalConf)
   183  	}
   184  
   185  	sessionStore := gs.redisStore
   186  	switch spec.SessionProvider.StorageEngine {
   187  	case RPCStorageEngine:
   188  		sessionStore = gs.rpcAuthStore
   189  	}
   190  
   191  	// Health checkers are initialised per spec so that each API handler has it's own connection and redis storage pool
   192  	spec.Init(authStore, sessionStore, gs.healthStore, orgStore)
   193  
   194  	// Set up all the JSVM middleware
   195  	var mwAuthCheckFunc apidef.MiddlewareDefinition
   196  	mwPreFuncs := []apidef.MiddlewareDefinition{}
   197  	mwPostFuncs := []apidef.MiddlewareDefinition{}
   198  	mwPostAuthCheckFuncs := []apidef.MiddlewareDefinition{}
   199  
   200  	var mwDriver apidef.MiddlewareDriver
   201  
   202  	var prefix string
   203  	if spec.CustomMiddlewareBundle != "" {
   204  		if err := loadBundle(spec); err != nil {
   205  			logger.WithError(err).Error("Couldn't load bundle")
   206  		}
   207  		prefix = getBundleDestPath(spec)
   208  	}
   209  
   210  	logger.Debug("Initializing API")
   211  	var mwPaths []string
   212  
   213  	mwPaths, mwAuthCheckFunc, mwPreFuncs, mwPostFuncs, mwPostAuthCheckFuncs, mwDriver = loadCustomMiddleware(spec)
   214  
   215  	if config.Global().EnableJSVM && mwDriver == apidef.OttoDriver {
   216  		spec.JSVM.LoadJSPaths(mwPaths, prefix)
   217  	}
   218  
   219  	//  if bundle was used - fix paths for goplugin-type custom middle-wares
   220  	if mwDriver == apidef.GoPluginDriver && prefix != "" {
   221  		mwAuthCheckFunc.Path = filepath.Join(prefix, mwAuthCheckFunc.Path)
   222  		fixFuncPath(prefix, mwPreFuncs)
   223  		fixFuncPath(prefix, mwPostFuncs)
   224  		fixFuncPath(prefix, mwPostAuthCheckFuncs)
   225  		// TODO: add mwResponseFuncs here when Golang response custom MW support implemented
   226  	}
   227  
   228  	if spec.EnableBatchRequestSupport {
   229  		addBatchEndpoint(spec, subrouter)
   230  	}
   231  
   232  	if spec.UseOauth2 {
   233  		logger.Debug("Loading OAuth Manager")
   234  		oauthManager := addOAuthHandlers(spec, subrouter)
   235  		logger.Debug("-- Added OAuth Handlers")
   236  
   237  		spec.OAuthManager = oauthManager
   238  		logger.Debug("Done loading OAuth Manager")
   239  	}
   240  
   241  	enableVersionOverrides := false
   242  	for _, versionData := range spec.VersionData.Versions {
   243  		if versionData.OverrideTarget != "" && !spec.VersionData.NotVersioned {
   244  			enableVersionOverrides = true
   245  			break
   246  		}
   247  	}
   248  
   249  	// Already vetted
   250  	spec.target, _ = url.Parse(spec.Proxy.TargetURL)
   251  
   252  	var proxy ReturningHttpHandler
   253  	if enableVersionOverrides {
   254  		logger.Info("Multi target enabled")
   255  		proxy = NewMultiTargetProxy(spec, logger)
   256  	} else {
   257  		proxy = TykNewSingleHostReverseProxy(spec.target, spec, logger)
   258  	}
   259  
   260  	// Create the response processors
   261  	createResponseMiddlewareChain(spec)
   262  
   263  	baseMid := BaseMiddleware{Spec: spec, Proxy: proxy, logger: logger}
   264  
   265  	for _, v := range baseMid.Spec.VersionData.Versions {
   266  		if len(v.ExtendedPaths.CircuitBreaker) > 0 {
   267  			baseMid.Spec.CircuitBreakerEnabled = true
   268  		}
   269  		if len(v.ExtendedPaths.HardTimeouts) > 0 {
   270  			baseMid.Spec.EnforcedTimeoutEnabled = true
   271  		}
   272  	}
   273  
   274  	keyPrefix := "cache-" + spec.APIID
   275  	cacheStore := storage.RedisCluster{KeyPrefix: keyPrefix, IsCache: true}
   276  	cacheStore.Connect()
   277  
   278  	var chain http.Handler
   279  	var chainArray []alice.Constructor
   280  	var authArray []alice.Constructor
   281  
   282  	if spec.UseKeylessAccess {
   283  		chainDef.Open = true
   284  		logger.Info("Checking security policy: Open")
   285  	}
   286  
   287  	handleCORS(&chainArray, spec)
   288  
   289  	for _, obj := range mwPreFuncs {
   290  		if mwDriver == apidef.GoPluginDriver {
   291  			mwAppendEnabled(
   292  				&chainArray,
   293  				&GoPluginMiddleware{
   294  					BaseMiddleware: baseMid,
   295  					Path:           obj.Path,
   296  					SymbolName:     obj.Name,
   297  				},
   298  			)
   299  		} else if mwDriver != apidef.OttoDriver {
   300  			coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
   301  			mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Pre, obj.Name, mwDriver, obj.RawBodyOnly, nil})
   302  		} else {
   303  			chainArray = append(chainArray, createDynamicMiddleware(obj.Name, true, obj.RequireSession, baseMid))
   304  		}
   305  	}
   306  
   307  	mwAppendEnabled(&chainArray, &VersionCheck{BaseMiddleware: baseMid})
   308  	mwAppendEnabled(&chainArray, &RateCheckMW{BaseMiddleware: baseMid})
   309  	mwAppendEnabled(&chainArray, &IPWhiteListMiddleware{BaseMiddleware: baseMid})
   310  	mwAppendEnabled(&chainArray, &IPBlackListMiddleware{BaseMiddleware: baseMid})
   311  	mwAppendEnabled(&chainArray, &CertificateCheckMW{BaseMiddleware: baseMid})
   312  	mwAppendEnabled(&chainArray, &OrganizationMonitor{BaseMiddleware: baseMid})
   313  	mwAppendEnabled(&chainArray, &RequestSizeLimitMiddleware{baseMid})
   314  	mwAppendEnabled(&chainArray, &MiddlewareContextVars{BaseMiddleware: baseMid})
   315  	mwAppendEnabled(&chainArray, &TrackEndpointMiddleware{baseMid})
   316  
   317  	if !spec.UseKeylessAccess {
   318  		// Select the keying method to use for setting session states
   319  		if mwAppendEnabled(&authArray, &Oauth2KeyExists{baseMid}) {
   320  			logger.Info("Checking security policy: OAuth")
   321  		}
   322  
   323  		if mwAppendEnabled(&authArray, &BasicAuthKeyIsValid{baseMid, nil, nil}) {
   324  			logger.Info("Checking security policy: Basic")
   325  		}
   326  
   327  		if mwAppendEnabled(&authArray, &HTTPSignatureValidationMiddleware{BaseMiddleware: baseMid}) {
   328  			logger.Info("Checking security policy: HMAC")
   329  		}
   330  
   331  		if mwAppendEnabled(&authArray, &JWTMiddleware{baseMid}) {
   332  			logger.Info("Checking security policy: JWT")
   333  		}
   334  
   335  		if mwAppendEnabled(&authArray, &OpenIDMW{BaseMiddleware: baseMid}) {
   336  			logger.Info("Checking security policy: OpenID")
   337  		}
   338  
   339  		coprocessAuth := mwDriver != apidef.OttoDriver && spec.EnableCoProcessAuth
   340  		ottoAuth := !coprocessAuth && mwDriver == apidef.OttoDriver && spec.EnableCoProcessAuth
   341  		gopluginAuth := !coprocessAuth && !ottoAuth && mwDriver == apidef.GoPluginDriver && spec.UseGoPluginAuth
   342  		if coprocessAuth {
   343  			// TODO: check if mwAuthCheckFunc is available/valid
   344  			coprocessLog.Debug("Registering coprocess middleware, hook name: ", mwAuthCheckFunc.Name, "hook type: CustomKeyCheck", ", driver: ", mwDriver)
   345  
   346  			newExtractor(spec, baseMid)
   347  			mwAppendEnabled(&authArray, &CoProcessMiddleware{baseMid, coprocess.HookType_CustomKeyCheck, mwAuthCheckFunc.Name, mwDriver, mwAuthCheckFunc.RawBodyOnly, nil})
   348  		}
   349  
   350  		if ottoAuth {
   351  			logger.Info("----> Checking security policy: JS Plugin")
   352  			authArray = append(authArray, createMiddleware(&DynamicMiddleware{
   353  				BaseMiddleware:      baseMid,
   354  				MiddlewareClassName: mwAuthCheckFunc.Name,
   355  				Pre:                 true,
   356  				Auth:                true,
   357  			}))
   358  		}
   359  
   360  		if gopluginAuth {
   361  			mwAppendEnabled(
   362  				&authArray,
   363  				&GoPluginMiddleware{
   364  					BaseMiddleware: baseMid,
   365  					Path:           mwAuthCheckFunc.Path,
   366  					SymbolName:     mwAuthCheckFunc.Name,
   367  				},
   368  			)
   369  		}
   370  
   371  		if spec.UseStandardAuth || len(authArray) == 0 {
   372  			logger.Info("Checking security policy: Token")
   373  			authArray = append(authArray, createMiddleware(&AuthKey{baseMid}))
   374  		}
   375  
   376  		chainArray = append(chainArray, authArray...)
   377  
   378  		for _, obj := range mwPostAuthCheckFuncs {
   379  			if mwDriver == apidef.GoPluginDriver {
   380  				mwAppendEnabled(
   381  					&chainArray,
   382  					&GoPluginMiddleware{
   383  						BaseMiddleware: baseMid,
   384  						Path:           obj.Path,
   385  						SymbolName:     obj.Name,
   386  					},
   387  				)
   388  			} else {
   389  				coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Pre", ", driver: ", mwDriver)
   390  				mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_PostKeyAuth, obj.Name, mwDriver, obj.RawBodyOnly, nil})
   391  			}
   392  		}
   393  
   394  		mwAppendEnabled(&chainArray, &StripAuth{baseMid})
   395  		mwAppendEnabled(&chainArray, &KeyExpired{baseMid})
   396  		mwAppendEnabled(&chainArray, &AccessRightsCheck{baseMid})
   397  		mwAppendEnabled(&chainArray, &GranularAccessMiddleware{baseMid})
   398  		mwAppendEnabled(&chainArray, &RateLimitAndQuotaCheck{baseMid})
   399  	}
   400  
   401  	mwAppendEnabled(&chainArray, &RateLimitForAPI{BaseMiddleware: baseMid})
   402  	mwAppendEnabled(&chainArray, &ValidateJSON{BaseMiddleware: baseMid})
   403  	mwAppendEnabled(&chainArray, &TransformMiddleware{baseMid})
   404  	mwAppendEnabled(&chainArray, &TransformJQMiddleware{baseMid})
   405  	mwAppendEnabled(&chainArray, &TransformHeaders{BaseMiddleware: baseMid})
   406  	mwAppendEnabled(&chainArray, &URLRewriteMiddleware{BaseMiddleware: baseMid})
   407  	mwAppendEnabled(&chainArray, &TransformMethod{BaseMiddleware: baseMid})
   408  	mwAppendEnabled(&chainArray, &VirtualEndpoint{BaseMiddleware: baseMid})
   409  	mwAppendEnabled(&chainArray, &RequestSigning{BaseMiddleware: baseMid})
   410  
   411  	for _, obj := range mwPostFuncs {
   412  		if mwDriver == apidef.GoPluginDriver {
   413  			mwAppendEnabled(
   414  				&chainArray,
   415  				&GoPluginMiddleware{
   416  					BaseMiddleware: baseMid,
   417  					Path:           obj.Path,
   418  					SymbolName:     obj.Name,
   419  				},
   420  			)
   421  		} else if mwDriver != apidef.OttoDriver {
   422  			coprocessLog.Debug("Registering coprocess middleware, hook name: ", obj.Name, "hook type: Post", ", driver: ", mwDriver)
   423  			mwAppendEnabled(&chainArray, &CoProcessMiddleware{baseMid, coprocess.HookType_Post, obj.Name, mwDriver, obj.RawBodyOnly, nil})
   424  		} else {
   425  			chainArray = append(chainArray, createDynamicMiddleware(obj.Name, false, obj.RequireSession, baseMid))
   426  		}
   427  	}
   428  	//Do not add middlewares after cache middleware.
   429  	//It will not get executed
   430  	mwAppendEnabled(&chainArray, &RedisCacheMiddleware{BaseMiddleware: baseMid, CacheStore: &cacheStore})
   431  	chain = alice.New(chainArray...).Then(&DummyProxyHandler{SH: SuccessHandler{baseMid}})
   432  
   433  	if !spec.UseKeylessAccess {
   434  		var simpleArray []alice.Constructor
   435  		mwAppendEnabled(&simpleArray, &IPWhiteListMiddleware{baseMid})
   436  		mwAppendEnabled(&simpleArray, &IPBlackListMiddleware{BaseMiddleware: baseMid})
   437  		mwAppendEnabled(&simpleArray, &OrganizationMonitor{BaseMiddleware: baseMid})
   438  		mwAppendEnabled(&simpleArray, &VersionCheck{BaseMiddleware: baseMid})
   439  		simpleArray = append(simpleArray, authArray...)
   440  		mwAppendEnabled(&simpleArray, &KeyExpired{baseMid})
   441  		mwAppendEnabled(&simpleArray, &AccessRightsCheck{baseMid})
   442  
   443  		rateLimitPath := spec.Proxy.ListenPath + "tyk/rate-limits/"
   444  
   445  		logger.Debug("Rate limit endpoint is: ", rateLimitPath)
   446  
   447  		chainDef.RateLimitPath = rateLimitPath
   448  		chainDef.RateLimitChain = alice.New(simpleArray...).
   449  			Then(http.HandlerFunc(userRatesCheck))
   450  	}
   451  
   452  	logger.Debug("Setting Listen Path: ", spec.Proxy.ListenPath)
   453  
   454  	if trace.IsEnabled() {
   455  		chainDef.ThisHandler = trace.Handle(spec.Name, chain)
   456  	} else {
   457  		chainDef.ThisHandler = chain
   458  	}
   459  	chainDef.ListenOn = spec.Proxy.ListenPath + "{rest:.*}"
   460  	chainDef.Domain = spec.Domain
   461  
   462  	logger.WithFields(logrus.Fields{
   463  		"prefix":      "gateway",
   464  		"user_ip":     "--",
   465  		"server_name": "--",
   466  		"user_id":     "--",
   467  	}).Info("API Loaded")
   468  
   469  	return &chainDef
   470  }
   471  
   472  // Check for recursion
   473  const defaultLoopLevelLimit = 5
   474  
   475  func isLoop(r *http.Request) (bool, error) {
   476  	if r.URL.Scheme != "tyk" {
   477  		return false, nil
   478  	}
   479  
   480  	limit := ctxLoopLevelLimit(r)
   481  	if limit == 0 {
   482  		limit = defaultLoopLevelLimit
   483  	}
   484  
   485  	if ctxLoopLevel(r) > limit {
   486  		return true, fmt.Errorf("Loop level too deep. Found more than %d loops in single request", limit)
   487  	}
   488  
   489  	return true, nil
   490  }
   491  
   492  type DummyProxyHandler struct {
   493  	SH SuccessHandler
   494  }
   495  
   496  func (d *DummyProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
   497  	if newURL := ctxGetURLRewriteTarget(r); newURL != nil {
   498  		r.URL = newURL
   499  		ctxSetURLRewriteTarget(r, nil)
   500  	}
   501  	if newMethod := ctxGetTransformRequestMethod(r); newMethod != "" {
   502  		r.Method = newMethod
   503  		ctxSetTransformRequestMethod(r, "")
   504  	}
   505  	if found, err := isLoop(r); found {
   506  		if err != nil {
   507  			handler := ErrorHandler{*d.SH.Base()}
   508  			handler.HandleError(w, r, err.Error(), http.StatusInternalServerError, true)
   509  			return
   510  		}
   511  
   512  		r.URL.Scheme = "http"
   513  		if methodOverride := r.URL.Query().Get("method"); methodOverride != "" {
   514  			r.Method = methodOverride
   515  		}
   516  
   517  		var handler http.Handler
   518  		if r.URL.Hostname() == "self" {
   519  			if h, found := apisHandlesByID.Load(d.SH.Spec.APIID); found {
   520  				handler = h.(http.Handler)
   521  			}
   522  		} else {
   523  			ctxSetVersionInfo(r, nil)
   524  
   525  			if targetAPI := fuzzyFindAPI(r.URL.Hostname()); targetAPI != nil {
   526  				if h, found := apisHandlesByID.Load(targetAPI.APIID); found {
   527  					handler = h.(http.Handler)
   528  				}
   529  			} else {
   530  				handler := ErrorHandler{*d.SH.Base()}
   531  				handler.HandleError(w, r, "Can't detect loop target", http.StatusInternalServerError, true)
   532  				return
   533  			}
   534  		}
   535  
   536  		// No need to handle errors, in all error cases limit will be set to 0
   537  		loopLevelLimit, _ := strconv.Atoi(r.URL.Query().Get("loop_limit"))
   538  		ctxSetCheckLoopLimits(r, r.URL.Query().Get("check_limits") == "true")
   539  
   540  		if origURL := ctxGetOrigRequestURL(r); origURL != nil {
   541  			r.URL.Host = origURL.Host
   542  			r.URL.RawQuery = origURL.RawQuery
   543  			ctxSetOrigRequestURL(r, nil)
   544  		}
   545  
   546  		ctxIncLoopLevel(r, loopLevelLimit)
   547  
   548  		handler.ServeHTTP(w, r)
   549  	} else {
   550  		d.SH.ServeHTTP(w, r)
   551  	}
   552  }
   553  
   554  func loadGlobalApps() {
   555  	// we need to make a full copy of the slice, as loadApps will
   556  	// use in-place to sort the apis.
   557  	apisMu.RLock()
   558  	specs := make([]*APISpec, len(apiSpecs))
   559  	copy(specs, apiSpecs)
   560  	apisMu.RUnlock()
   561  	loadApps(specs)
   562  }
   563  
   564  func trimCategories(name string) string {
   565  	if i := strings.Index(name, "#"); i != -1 {
   566  		return name[:i-1]
   567  	}
   568  
   569  	return name
   570  }
   571  
   572  func fuzzyFindAPI(search string) *APISpec {
   573  	if search == "" {
   574  		return nil
   575  	}
   576  
   577  	apisMu.RLock()
   578  	defer apisMu.RUnlock()
   579  
   580  	for _, api := range apisByID {
   581  		if api.APIID == search ||
   582  			api.Id.Hex() == search ||
   583  			replaceNonAlphaNumeric(trimCategories(api.Name)) == search {
   584  			return api
   585  		}
   586  	}
   587  
   588  	return nil
   589  }
   590  
   591  func loadHTTPService(spec *APISpec, apisByListen map[string]int, gs *generalStores, muxer *proxyMux) http.Handler {
   592  	port := config.Global().ListenPort
   593  	if spec.ListenPort != 0 {
   594  		port = spec.ListenPort
   595  	}
   596  	router := muxer.router(port, spec.Protocol)
   597  	if router == nil {
   598  		router = mux.NewRouter()
   599  		muxer.setRouter(port, spec.Protocol, router)
   600  	}
   601  
   602  	hostname := config.Global().HostName
   603  	if config.Global().EnableCustomDomains && spec.Domain != "" {
   604  		hostname = spec.Domain
   605  	}
   606  
   607  	if hostname != "" {
   608  		mainLog.Info("API hostname set: ", hostname)
   609  		router = router.Host(hostname).Subrouter()
   610  	}
   611  
   612  	chainObj := processSpec(spec, apisByListen, gs, router, logrus.NewEntry(log))
   613  
   614  	if chainObj.Skip {
   615  		return chainObj.ThisHandler
   616  	}
   617  
   618  	if !chainObj.Open {
   619  		router.Handle(chainObj.RateLimitPath, chainObj.RateLimitChain)
   620  	}
   621  
   622  	router.Handle(chainObj.ListenOn, chainObj.ThisHandler)
   623  
   624  	return chainObj.ThisHandler
   625  }
   626  
   627  func loadTCPService(spec *APISpec, gs *generalStores, muxer *proxyMux) {
   628  	// Initialise the auth and session managers (use Redis for now)
   629  	authStore := gs.redisStore
   630  	orgStore := gs.redisOrgStore
   631  	switch spec.AuthProvider.StorageEngine {
   632  	case LDAPStorageEngine:
   633  		storageEngine := LDAPStorageHandler{}
   634  		storageEngine.LoadConfFromMeta(spec.AuthProvider.Meta)
   635  		authStore = &storageEngine
   636  	case RPCStorageEngine:
   637  		authStore = gs.rpcAuthStore
   638  		orgStore = gs.rpcOrgStore
   639  		spec.GlobalConfig.EnforceOrgDataAge = true
   640  		globalConf := config.Global()
   641  		globalConf.EnforceOrgDataAge = true
   642  		config.SetGlobal(globalConf)
   643  	}
   644  
   645  	sessionStore := gs.redisStore
   646  	switch spec.SessionProvider.StorageEngine {
   647  	case RPCStorageEngine:
   648  		sessionStore = gs.rpcAuthStore
   649  	}
   650  
   651  	// Health checkers are initialised per spec so that each API handler has it's own connection and redis storage pool
   652  	spec.Init(authStore, sessionStore, gs.healthStore, orgStore)
   653  
   654  	muxer.addTCPService(spec, nil)
   655  }
   656  
   657  type generalStores struct {
   658  	redisStore, redisOrgStore, healthStore, rpcAuthStore, rpcOrgStore storage.Handler
   659  }
   660  
   661  // Create the individual API (app) specs based on live configurations and assign middleware
   662  func loadApps(specs []*APISpec) {
   663  	mainLog.Info("Loading API configurations.")
   664  
   665  	tmpSpecRegister := make(map[string]*APISpec)
   666  	tmpSpecHandles := new(sync.Map)
   667  
   668  	// sort by listen path from longer to shorter, so that /foo
   669  	// doesn't break /foo-bar
   670  	sort.Slice(specs, func(i, j int) bool {
   671  		return len(specs[i].Proxy.ListenPath) > len(specs[j].Proxy.ListenPath)
   672  	})
   673  
   674  	// Create a new handler for each API spec
   675  	apisByListen := countApisByListenHash(specs)
   676  
   677  	muxer := &proxyMux{}
   678  
   679  	globalConf := config.Global()
   680  	r := mux.NewRouter()
   681  	muxer.setRouter(globalConf.ListenPort, "", r)
   682  	if globalConf.ControlAPIPort == 0 {
   683  		loadAPIEndpoints(r)
   684  	} else {
   685  		router := mux.NewRouter()
   686  		loadAPIEndpoints(router)
   687  		muxer.setRouter(globalConf.ControlAPIPort, "", router)
   688  	}
   689  	gs := prepareStorage()
   690  	shouldTrace := trace.IsEnabled()
   691  	for _, spec := range specs {
   692  		if spec.ListenPort != spec.GlobalConfig.ListenPort {
   693  			mainLog.Info("API bind on custom port:", spec.ListenPort)
   694  		}
   695  
   696  		tmpSpecRegister[spec.APIID] = spec
   697  
   698  		switch spec.Protocol {
   699  		case "", "http", "https":
   700  			if shouldTrace {
   701  				// opentracing works only with http services.
   702  				err := trace.AddTracer("", spec.Name)
   703  				if err != nil {
   704  					mainLog.Errorf("Failed to initialize tracer for %q error:%v", spec.Name, err)
   705  				} else {
   706  					mainLog.Infof("Intialized tracer  api_name=%q", spec.Name)
   707  				}
   708  			}
   709  			tmpSpecHandles.Store(spec.APIID, loadHTTPService(spec, apisByListen, &gs, muxer))
   710  		case "tcp", "tls":
   711  			loadTCPService(spec, &gs, muxer)
   712  		}
   713  	}
   714  
   715  	defaultProxyMux.swap(muxer)
   716  
   717  	// Swap in the new register
   718  	apisMu.Lock()
   719  
   720  	// release current specs resources before overwriting map
   721  	for _, curSpec := range apisByID {
   722  		curSpec.Release()
   723  	}
   724  
   725  	apisByID = tmpSpecRegister
   726  	apisHandlesByID = tmpSpecHandles
   727  
   728  	apisMu.Unlock()
   729  
   730  	mainLog.Debug("Checker host list")
   731  
   732  	// Kick off our host checkers
   733  	if !config.Global().UptimeTests.Disable {
   734  		SetCheckerHostList()
   735  	}
   736  
   737  	mainLog.Debug("Checker host Done")
   738  
   739  	mainLog.Info("Initialised API Definitions")
   740  }