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 }