github.com/anuvu/tyk@v2.9.0-beta9-dl-apic+incompatible/gateway/api_definition.go (about) 1 package gateway 2 3 import ( 4 "encoding/base64" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "net/url" 12 "os" 13 "path/filepath" 14 "strings" 15 "sync" 16 "sync/atomic" 17 "text/template" 18 "time" 19 20 sprig "gopkg.in/Masterminds/sprig.v2" 21 22 "github.com/TykTechnologies/tyk/headers" 23 "github.com/TykTechnologies/tyk/rpc" 24 25 circuit "github.com/rubyist/circuitbreaker" 26 "github.com/sirupsen/logrus" 27 28 "github.com/TykTechnologies/gojsonschema" 29 "github.com/TykTechnologies/tyk/apidef" 30 "github.com/TykTechnologies/tyk/config" 31 "github.com/TykTechnologies/tyk/regexp" 32 "github.com/TykTechnologies/tyk/storage" 33 "github.com/garyburd/redigo/redis" 34 ) 35 36 //const used by cache middleware 37 const SAFE_METHODS = "SAFE_METHODS" 38 39 const ( 40 LDAPStorageEngine apidef.StorageEngineCode = "ldap" 41 RPCStorageEngine apidef.StorageEngineCode = "rpc" 42 ) 43 44 // Constants used by the version check middleware 45 const ( 46 headerLocation = "header" 47 urlParamLocation = "url-param" 48 urlLocation = "url" 49 expiredTimeFormat = "2006-01-02 15:04" 50 ) 51 52 // URLStatus is a custom enum type to avoid collisions 53 type URLStatus int 54 55 // Enums representing the various statuses for a VersionInfo Path match during a 56 // proxy request 57 const ( 58 _ URLStatus = iota 59 Ignored 60 WhiteList 61 BlackList 62 Cached 63 Transformed 64 TransformedJQ 65 HeaderInjected 66 HeaderInjectedResponse 67 TransformedResponse 68 TransformedJQResponse 69 HardTimeout 70 CircuitBreaker 71 URLRewrite 72 VirtualPath 73 RequestSizeLimit 74 MethodTransformed 75 RequestTracked 76 RequestNotTracked 77 ValidateJSONRequest 78 Internal 79 ) 80 81 // RequestStatus is a custom type to avoid collisions 82 type RequestStatus string 83 84 // Statuses of the request, all are false-y except StatusOk and StatusOkAndIgnore 85 const ( 86 VersionNotFound RequestStatus = "Version information not found" 87 VersionDoesNotExist RequestStatus = "This API version does not seem to exist" 88 VersionWhiteListStatusNotFound RequestStatus = "WhiteListStatus for path not found" 89 VersionExpired RequestStatus = "Api Version has expired, please check documentation or contact administrator" 90 EndPointNotAllowed RequestStatus = "Requested endpoint is forbidden" 91 StatusOkAndIgnore RequestStatus = "Everything OK, passing and not filtering" 92 StatusOk RequestStatus = "Everything OK, passing" 93 StatusCached RequestStatus = "Cached path" 94 StatusTransform RequestStatus = "Transformed path" 95 StatusTransformResponse RequestStatus = "Transformed response" 96 StatusTransformJQ RequestStatus = "Transformed path with JQ" 97 StatusTransformJQResponse RequestStatus = "Transformed response with JQ" 98 StatusHeaderInjected RequestStatus = "Header injected" 99 StatusMethodTransformed RequestStatus = "Method Transformed" 100 StatusHeaderInjectedResponse RequestStatus = "Header injected on response" 101 StatusRedirectFlowByReply RequestStatus = "Exceptional action requested, redirecting flow!" 102 StatusHardTimeout RequestStatus = "Hard Timeout enforced on path" 103 StatusCircuitBreaker RequestStatus = "Circuit breaker enforced" 104 StatusURLRewrite RequestStatus = "URL Rewritten" 105 StatusVirtualPath RequestStatus = "Virtual Endpoint" 106 StatusRequestSizeControlled RequestStatus = "Request Size Limited" 107 StatusRequestTracked RequestStatus = "Request Tracked" 108 StatusRequestNotTracked RequestStatus = "Request Not Tracked" 109 StatusValidateJSON RequestStatus = "Validate JSON" 110 StatusInternal RequestStatus = "Internal path" 111 ) 112 113 // URLSpec represents a flattened specification for URLs, used to check if a proxy URL 114 // path is on any of the white, black or ignored lists. This is generated as part of the 115 // configuration init 116 type URLSpec struct { 117 Spec *regexp.Regexp 118 Status URLStatus 119 MethodActions map[string]apidef.EndpointMethodMeta 120 CacheConfig EndPointCacheMeta 121 TransformAction TransformSpec 122 TransformResponseAction TransformSpec 123 TransformJQAction TransformJQSpec 124 TransformJQResponseAction TransformJQSpec 125 InjectHeaders apidef.HeaderInjectionMeta 126 InjectHeadersResponse apidef.HeaderInjectionMeta 127 HardTimeout apidef.HardTimeoutMeta 128 CircuitBreaker ExtendedCircuitBreakerMeta 129 URLRewrite *apidef.URLRewriteMeta 130 VirtualPathSpec apidef.VirtualMeta 131 RequestSize apidef.RequestSizeMeta 132 MethodTransform apidef.MethodTransformMeta 133 TrackEndpoint apidef.TrackEndpointMeta 134 DoNotTrackEndpoint apidef.TrackEndpointMeta 135 ValidatePathMeta apidef.ValidatePathMeta 136 Internal apidef.InternalMeta 137 } 138 139 type EndPointCacheMeta struct { 140 Method string 141 CacheKeyRegex string 142 } 143 144 type TransformSpec struct { 145 apidef.TemplateMeta 146 Template *template.Template 147 } 148 149 type ExtendedCircuitBreakerMeta struct { 150 apidef.CircuitBreakerMeta 151 CB *circuit.Breaker `json:"-"` 152 } 153 154 // APISpec represents a path specification for an API, to avoid enumerating multiple nested lists, a single 155 // flattened URL list is checked for matching paths and then it's status evaluated if found. 156 type APISpec struct { 157 *apidef.APIDefinition 158 sync.RWMutex 159 160 RxPaths map[string][]URLSpec 161 WhiteListEnabled map[string]bool 162 target *url.URL 163 AuthManager AuthorisationHandler 164 SessionManager SessionHandler 165 OAuthManager *OAuthManager 166 OrgSessionManager SessionHandler 167 EventPaths map[apidef.TykEvent][]config.TykEventHandler 168 Health HealthChecker 169 JSVM JSVM 170 ResponseChain []TykResponseHandler 171 RoundRobin RoundRobin 172 URLRewriteEnabled bool 173 CircuitBreakerEnabled bool 174 EnforcedTimeoutEnabled bool 175 LastGoodHostList *apidef.HostList 176 HasRun bool 177 ServiceRefreshInProgress bool 178 HTTPTransport http.RoundTripper 179 HTTPTransportCreated time.Time 180 WSTransport http.RoundTripper 181 WSTransportCreated time.Time 182 GlobalConfig config.Config 183 OrgHasNoSession bool 184 185 middlewareChain http.Handler 186 187 shouldRelease bool 188 } 189 190 // Release re;leases all resources associated with API spec 191 func (s *APISpec) Release() { 192 // mark spec as to be released 193 s.shouldRelease = true 194 195 // release circuit breaker resources 196 for _, path := range s.RxPaths { 197 for _, urlSpec := range path { 198 if urlSpec.CircuitBreaker.CB != nil { 199 // this will force CB-event reading routing to exit 200 urlSpec.CircuitBreaker.CB.Reset() 201 202 // TODO: close all circuit breaker's event receivers 203 // which should let event reading Go-routines to exit (need to change circuitbreaker package) 204 } 205 } 206 } 207 208 // release all other resources associated with spec 209 } 210 211 // APIDefinitionLoader will load an Api definition from a storage 212 // system. 213 type APIDefinitionLoader struct{} 214 215 // Nonce to use when interacting with the dashboard service 216 var ServiceNonce string 217 218 // MakeSpec will generate a flattened URLSpec from and APIDefinitions' VersionInfo data. paths are 219 // keyed to the Api version name, which is determined during routing to speed up lookups 220 func (a APIDefinitionLoader) MakeSpec(def *apidef.APIDefinition, logger *logrus.Entry) *APISpec { 221 spec := &APISpec{} 222 223 if logger == nil { 224 logger = logrus.NewEntry(log) 225 } 226 227 // parse version expiration time stamps 228 for key, ver := range def.VersionData.Versions { 229 if ver.Expires == "" || ver.Expires == "-1" { 230 continue 231 } 232 // calculate the time 233 if t, err := time.Parse(expiredTimeFormat, ver.Expires); err != nil { 234 logger.WithError(err).WithField("Expires", ver.Expires).Error("Could not parse expiry date for API") 235 } else { 236 ver.ExpiresTs = t 237 def.VersionData.Versions[key] = ver 238 } 239 } 240 241 spec.APIDefinition = def 242 243 // We'll push the default HealthChecker: 244 spec.Health = &DefaultHealthChecker{ 245 APIID: spec.APIID, 246 } 247 248 // Add any new session managers or auth handlers here 249 spec.AuthManager = &DefaultAuthorisationManager{} 250 251 spec.SessionManager = &DefaultSessionManager{} 252 spec.OrgSessionManager = &DefaultSessionManager{} 253 254 spec.GlobalConfig = config.Global() 255 256 // Create and init the virtual Machine 257 if config.Global().EnableJSVM { 258 spec.JSVM.Init(spec, logger) 259 } 260 261 // Set up Event Handlers 262 if len(def.EventHandlers.Events) > 0 { 263 logger.Debug("Initializing event handlers") 264 } 265 spec.EventPaths = make(map[apidef.TykEvent][]config.TykEventHandler) 266 for eventName, eventHandlerConfs := range def.EventHandlers.Events { 267 logger.Debug("FOUND EVENTS TO INIT") 268 for _, handlerConf := range eventHandlerConfs { 269 logger.Debug("CREATING EVENT HANDLERS") 270 eventHandlerInstance, err := EventHandlerByName(handlerConf, spec) 271 272 if err != nil { 273 logger.Error("Failed to init event handler: ", err) 274 } else { 275 logger.Debug("Init Event Handler: ", eventName) 276 spec.EventPaths[eventName] = append(spec.EventPaths[eventName], eventHandlerInstance) 277 } 278 279 } 280 } 281 282 spec.RxPaths = make(map[string][]URLSpec, len(def.VersionData.Versions)) 283 spec.WhiteListEnabled = make(map[string]bool, len(def.VersionData.Versions)) 284 for _, v := range def.VersionData.Versions { 285 var pathSpecs []URLSpec 286 var whiteListSpecs bool 287 288 // If we have transitioned to extended path specifications, we should use these now 289 if v.UseExtendedPaths { 290 pathSpecs, whiteListSpecs = a.getExtendedPathSpecs(v, spec) 291 } else { 292 logger.Warning("Legacy path detected! Upgrade to extended.") 293 pathSpecs, whiteListSpecs = a.getPathSpecs(v) 294 } 295 spec.RxPaths[v.Name] = pathSpecs 296 spec.WhiteListEnabled[v.Name] = whiteListSpecs 297 } 298 299 return spec 300 } 301 302 // FromDashboardService will connect and download ApiDefintions from a Tyk Dashboard instance. 303 func (a APIDefinitionLoader) FromDashboardService(endpoint, secret string) ([]*APISpec, error) { 304 // Get the definitions 305 log.Debug("Calling: ", endpoint) 306 newRequest, err := http.NewRequest("GET", endpoint, nil) 307 if err != nil { 308 log.Error("Failed to create request: ", err) 309 } 310 311 newRequest.Header.Set("authorization", secret) 312 log.Debug("Using: NodeID: ", getNodeID()) 313 newRequest.Header.Set(headers.XTykNodeID, getNodeID()) 314 315 newRequest.Header.Set(headers.XTykNonce, ServiceNonce) 316 317 c := initialiseClient(120 * time.Second) 318 resp, err := c.Do(newRequest) 319 if err != nil { 320 return nil, err 321 } 322 defer resp.Body.Close() 323 324 if resp.StatusCode == http.StatusForbidden { 325 body, _ := ioutil.ReadAll(resp.Body) 326 reLogin() 327 return nil, fmt.Errorf("login failure, Response was: %v", string(body)) 328 } 329 330 if resp.StatusCode != http.StatusOK { 331 body, _ := ioutil.ReadAll(resp.Body) 332 reLogin() 333 return nil, fmt.Errorf("dashboard API error, response was: %v", string(body)) 334 } 335 336 // Extract tagged APIs# 337 var list struct { 338 Message []struct { 339 ApiDefinition *apidef.APIDefinition `bson:"api_definition" json:"api_definition"` 340 } 341 Nonce string 342 } 343 if err := json.NewDecoder(resp.Body).Decode(&list); err != nil { 344 body, _ := ioutil.ReadAll(resp.Body) 345 return nil, fmt.Errorf("failed to decode body: %v body was: %v", err, string(body)) 346 } 347 348 // Extract tagged entries only 349 apiDefs := make([]*apidef.APIDefinition, 0) 350 351 if config.Global().DBAppConfOptions.NodeIsSegmented { 352 tagList := make(map[string]bool, len(config.Global().DBAppConfOptions.Tags)) 353 toLoad := make(map[string]*apidef.APIDefinition) 354 355 for _, mt := range config.Global().DBAppConfOptions.Tags { 356 tagList[mt] = true 357 } 358 359 for _, apiEntry := range list.Message { 360 for _, t := range apiEntry.ApiDefinition.Tags { 361 if tagList[t] { 362 toLoad[apiEntry.ApiDefinition.APIID] = apiEntry.ApiDefinition 363 } 364 } 365 } 366 367 for _, apiDef := range toLoad { 368 apiDefs = append(apiDefs, apiDef) 369 } 370 } else { 371 for _, apiEntry := range list.Message { 372 apiDefs = append(apiDefs, apiEntry.ApiDefinition) 373 } 374 } 375 376 // Process 377 var specs []*APISpec 378 for _, def := range apiDefs { 379 spec := a.MakeSpec(def, nil) 380 specs = append(specs, spec) 381 } 382 383 // Set the nonce 384 ServiceNonce = list.Nonce 385 log.Debug("Loading APIS Finished: Nonce Set: ", ServiceNonce) 386 387 return specs, nil 388 } 389 390 // FromCloud will connect and download ApiDefintions from a Mongo DB instance. 391 func (a APIDefinitionLoader) FromRPC(orgId string) ([]*APISpec, error) { 392 if rpc.IsEmergencyMode() { 393 return LoadDefinitionsFromRPCBackup() 394 } 395 396 store := RPCStorageHandler{} 397 if !store.Connect() { 398 return nil, errors.New("Can't connect RPC layer") 399 } 400 401 // enable segments 402 var tags []string 403 if config.Global().DBAppConfOptions.NodeIsSegmented { 404 log.Info("Segmented node, loading: ", config.Global().DBAppConfOptions.Tags) 405 tags = config.Global().DBAppConfOptions.Tags 406 } 407 408 apiCollection := store.GetApiDefinitions(orgId, tags) 409 410 //store.Disconnect() 411 412 if rpc.LoadCount() > 0 { 413 if err := saveRPCDefinitionsBackup(apiCollection); err != nil { 414 return nil, err 415 } 416 } 417 418 return a.processRPCDefinitions(apiCollection) 419 } 420 421 // FromCloud will connect and download ApiDefintions from a Mongo DB instance. 422 func (a APIDefinitionLoader) FromRedis(db config.RedisDBAppConfOptionsConfig) ([]*APISpec, error) { 423 var specs []*APISpec 424 c := RedisPool.Get() 425 defer c.Close() 426 log.Info("Loading API Specification from redis") 427 428 // Load API Definition from Redis DB 429 apiKeys, err := redis.Strings(c.Do("KEYS", "*")) 430 if err != nil { 431 log.Error("Couldn't get api definition from redis db: ", err) 432 } 433 for _, v := range apiKeys { 434 apiDefinition, _ := redis.String(c.Do("GET", v)) 435 def := a.ParseDefinition(strings.NewReader(apiDefinition)) 436 spec := a.MakeSpec(def, nil) 437 specs = append(specs, spec) 438 } 439 return specs, nil 440 } 441 442 func (a APIDefinitionLoader) processRPCDefinitions(apiCollection string) ([]*APISpec, error) { 443 444 var apiDefs []*apidef.APIDefinition 445 if err := json.Unmarshal([]byte(apiCollection), &apiDefs); err != nil { 446 return nil, err 447 } 448 449 var specs []*APISpec 450 for _, def := range apiDefs { 451 def.DecodeFromDB() 452 453 if config.Global().SlaveOptions.BindToSlugsInsteadOfListenPaths { 454 newListenPath := "/" + def.Slug //+ "/" 455 log.Warning("Binding to ", 456 newListenPath, 457 " instead of ", 458 def.Proxy.ListenPath) 459 460 def.Proxy.ListenPath = newListenPath 461 } 462 463 spec := a.MakeSpec(def, nil) 464 specs = append(specs, spec) 465 } 466 467 return specs, nil 468 } 469 470 func (a APIDefinitionLoader) ParseDefinition(r io.Reader) *apidef.APIDefinition { 471 def := &apidef.APIDefinition{} 472 if err := json.NewDecoder(r).Decode(def); err != nil { 473 log.Error("[RPC] --> Couldn't unmarshal api configuration: ", err) 474 } 475 return def 476 } 477 478 // FromDir will load APIDefinitions from a directory on the filesystem. Definitions need 479 // to be the JSON representation of APIDefinition object 480 func (a APIDefinitionLoader) FromDir(dir string) []*APISpec { 481 var specs []*APISpec 482 // Grab json files from directory 483 paths, _ := filepath.Glob(filepath.Join(dir, "*.json")) 484 for _, path := range paths { 485 log.Info("Loading API Specification from ", path) 486 f, err := os.Open(path) 487 if err != nil { 488 log.Error("Couldn't open api configuration file: ", err) 489 continue 490 } 491 def := a.ParseDefinition(f) 492 f.Close() 493 spec := a.MakeSpec(def, nil) 494 specs = append(specs, spec) 495 } 496 return specs 497 } 498 499 func (a APIDefinitionLoader) getPathSpecs(apiVersionDef apidef.VersionInfo) ([]URLSpec, bool) { 500 ignoredPaths := a.compilePathSpec(apiVersionDef.Paths.Ignored, Ignored) 501 blackListPaths := a.compilePathSpec(apiVersionDef.Paths.BlackList, BlackList) 502 whiteListPaths := a.compilePathSpec(apiVersionDef.Paths.WhiteList, WhiteList) 503 504 combinedPath := []URLSpec{} 505 combinedPath = append(combinedPath, ignoredPaths...) 506 combinedPath = append(combinedPath, blackListPaths...) 507 combinedPath = append(combinedPath, whiteListPaths...) 508 509 return combinedPath, len(whiteListPaths) > 0 510 } 511 512 func (a APIDefinitionLoader) generateRegex(stringSpec string, newSpec *URLSpec, specType URLStatus) { 513 apiLangIDsRegex := regexp.MustCompile(`{([^}]*)}`) 514 asRegexStr := apiLangIDsRegex.ReplaceAllString(stringSpec, `([^/]*)`) 515 asRegex, _ := regexp.Compile(asRegexStr) 516 newSpec.Status = specType 517 newSpec.Spec = asRegex 518 } 519 520 func (a APIDefinitionLoader) compilePathSpec(paths []string, specType URLStatus) []URLSpec { 521 // transform a configuration URL into an array of URLSpecs 522 // This way we can iterate the whole array once, on match we break with status 523 urlSpec := []URLSpec{} 524 525 for _, stringSpec := range paths { 526 newSpec := URLSpec{} 527 a.generateRegex(stringSpec, &newSpec, specType) 528 urlSpec = append(urlSpec, newSpec) 529 } 530 531 return urlSpec 532 } 533 534 func (a APIDefinitionLoader) compileExtendedPathSpec(paths []apidef.EndPointMeta, specType URLStatus) []URLSpec { 535 // transform an extended configuration URL into an array of URLSpecs 536 // This way we can iterate the whole array once, on match we break with status 537 urlSpec := []URLSpec{} 538 539 for _, stringSpec := range paths { 540 newSpec := URLSpec{} 541 a.generateRegex(stringSpec.Path, &newSpec, specType) 542 543 // Extend with method actions 544 newSpec.MethodActions = stringSpec.MethodActions 545 urlSpec = append(urlSpec, newSpec) 546 } 547 548 return urlSpec 549 } 550 551 func (a APIDefinitionLoader) compileCachedPathSpec(oldpaths []string, newpaths []apidef.CacheMeta) []URLSpec { 552 // transform an extended configuration URL into an array of URLSpecs 553 // This way we can iterate the whole array once, on match we break with status 554 urlSpec := []URLSpec{} 555 556 for _, stringSpec := range oldpaths { 557 newSpec := URLSpec{} 558 a.generateRegex(stringSpec, &newSpec, Cached) 559 newSpec.CacheConfig.Method = SAFE_METHODS 560 newSpec.CacheConfig.CacheKeyRegex = "" 561 // Extend with method actions 562 urlSpec = append(urlSpec, newSpec) 563 } 564 565 for _, spec := range newpaths { 566 newSpec := URLSpec{} 567 a.generateRegex(spec.Path, &newSpec, Cached) 568 newSpec.CacheConfig.Method = spec.Method 569 newSpec.CacheConfig.CacheKeyRegex = spec.CacheKeyRegex 570 // Extend with method actions 571 urlSpec = append(urlSpec, newSpec) 572 } 573 574 return urlSpec 575 } 576 577 func (a APIDefinitionLoader) filterSprigFuncs() template.FuncMap { 578 tmp := sprig.GenericFuncMap() 579 delete(tmp, "env") 580 delete(tmp, "expandenv") 581 582 return template.FuncMap(tmp) 583 } 584 585 func (a APIDefinitionLoader) loadFileTemplate(path string) (*template.Template, error) { 586 log.Debug("-- Loading template: ", path) 587 return apidef.Template.New("").Funcs(a.filterSprigFuncs()).ParseFiles(path) 588 } 589 590 func (a APIDefinitionLoader) loadBlobTemplate(blob string) (*template.Template, error) { 591 log.Debug("-- Loading blob") 592 uDec, err := base64.StdEncoding.DecodeString(blob) 593 if err != nil { 594 return nil, err 595 } 596 return apidef.Template.New("").Funcs(a.filterSprigFuncs()).Parse(string(uDec)) 597 } 598 599 func (a APIDefinitionLoader) compileTransformPathSpec(paths []apidef.TemplateMeta, stat URLStatus) []URLSpec { 600 // transform an extended configuration URL into an array of URLSpecs 601 // This way we can iterate the whole array once, on match we break with status 602 urlSpec := []URLSpec{} 603 604 log.Debug("Checking for transform paths...") 605 for _, stringSpec := range paths { 606 log.Debug("-- Generating path") 607 newSpec := URLSpec{} 608 a.generateRegex(stringSpec.Path, &newSpec, stat) 609 // Extend with template actions 610 611 newTransformSpec := TransformSpec{TemplateMeta: stringSpec} 612 613 // Load the templates 614 var err error 615 616 switch stringSpec.TemplateData.Mode { 617 case apidef.UseFile: 618 log.Debug("-- Using File mode") 619 newTransformSpec.Template, err = a.loadFileTemplate(stringSpec.TemplateData.TemplateSource) 620 case apidef.UseBlob: 621 log.Debug("-- Blob mode") 622 newTransformSpec.Template, err = a.loadBlobTemplate(stringSpec.TemplateData.TemplateSource) 623 default: 624 log.Warning("[Transform Templates] No template mode defined! Found: ", stringSpec.TemplateData.Mode) 625 err = errors.New("No valid template mode defined, must be either 'file' or 'blob'") 626 } 627 628 if stat == Transformed { 629 newSpec.TransformAction = newTransformSpec 630 } else { 631 newSpec.TransformResponseAction = newTransformSpec 632 } 633 634 if err == nil { 635 urlSpec = append(urlSpec, newSpec) 636 log.Debug("-- Loaded") 637 } else { 638 log.Error("Template load failure! Skipping transformation: ", err) 639 } 640 641 } 642 643 return urlSpec 644 } 645 646 func (a APIDefinitionLoader) compileInjectedHeaderSpec(paths []apidef.HeaderInjectionMeta, stat URLStatus) []URLSpec { 647 // transform an extended configuration URL into an array of URLSpecs 648 // This way we can iterate the whole array once, on match we break with status 649 urlSpec := []URLSpec{} 650 651 for _, stringSpec := range paths { 652 newSpec := URLSpec{} 653 a.generateRegex(stringSpec.Path, &newSpec, stat) 654 // Extend with method actions 655 if stat == HeaderInjected { 656 newSpec.InjectHeaders = stringSpec 657 } else { 658 newSpec.InjectHeadersResponse = stringSpec 659 } 660 661 urlSpec = append(urlSpec, newSpec) 662 } 663 664 return urlSpec 665 } 666 667 func (a APIDefinitionLoader) compileMethodTransformSpec(paths []apidef.MethodTransformMeta, stat URLStatus) []URLSpec { 668 // transform an extended configuration URL into an array of URLSpecs 669 // This way we can iterate the whole array once, on match we break with status 670 urlSpec := []URLSpec{} 671 672 for _, stringSpec := range paths { 673 newSpec := URLSpec{} 674 a.generateRegex(stringSpec.Path, &newSpec, stat) 675 newSpec.MethodTransform = stringSpec 676 677 urlSpec = append(urlSpec, newSpec) 678 } 679 680 return urlSpec 681 } 682 683 func (a APIDefinitionLoader) compileTimeoutPathSpec(paths []apidef.HardTimeoutMeta, stat URLStatus) []URLSpec { 684 // transform an extended configuration URL into an array of URLSpecs 685 // This way we can iterate the whole array once, on match we break with status 686 urlSpec := []URLSpec{} 687 688 for _, stringSpec := range paths { 689 newSpec := URLSpec{} 690 a.generateRegex(stringSpec.Path, &newSpec, stat) 691 // Extend with method actions 692 newSpec.HardTimeout = stringSpec 693 694 urlSpec = append(urlSpec, newSpec) 695 } 696 697 return urlSpec 698 } 699 700 func (a APIDefinitionLoader) compileRequestSizePathSpec(paths []apidef.RequestSizeMeta, stat URLStatus) []URLSpec { 701 // transform an extended configuration URL into an array of URLSpecs 702 // This way we can iterate the whole array once, on match we break with status 703 urlSpec := []URLSpec{} 704 705 for _, stringSpec := range paths { 706 newSpec := URLSpec{} 707 a.generateRegex(stringSpec.Path, &newSpec, stat) 708 // Extend with method actions 709 newSpec.RequestSize = stringSpec 710 711 urlSpec = append(urlSpec, newSpec) 712 } 713 714 return urlSpec 715 } 716 717 func (a APIDefinitionLoader) compileCircuitBreakerPathSpec(paths []apidef.CircuitBreakerMeta, stat URLStatus, apiSpec *APISpec) []URLSpec { 718 // transform an extended configuration URL into an array of URLSpecs 719 // This way we can iterate the whole array once, on match we break with status 720 urlSpec := []URLSpec{} 721 722 for _, stringSpec := range paths { 723 newSpec := URLSpec{} 724 a.generateRegex(stringSpec.Path, &newSpec, stat) 725 // Extend with method actions 726 newSpec.CircuitBreaker = ExtendedCircuitBreakerMeta{CircuitBreakerMeta: stringSpec} 727 log.Debug("Initialising circuit breaker for: ", stringSpec.Path) 728 newSpec.CircuitBreaker.CB = circuit.NewRateBreaker(stringSpec.ThresholdPercent, stringSpec.Samples) 729 events := newSpec.CircuitBreaker.CB.Subscribe() 730 go func(path string, spec *APISpec, breakerPtr *circuit.Breaker) { 731 timerActive := false 732 for e := range events { 733 switch e { 734 case circuit.BreakerTripped: 735 log.Warning("[PROXY] [CIRCUIT BREAKER] Breaker tripped for path: ", path) 736 log.Debug("Breaker tripped: ", e) 737 // Start a timer function 738 739 if !timerActive { 740 go func(timeout int, breaker *circuit.Breaker) { 741 log.Debug("-- Sleeping for (s): ", timeout) 742 time.Sleep(time.Duration(timeout) * time.Second) 743 log.Debug("-- Resetting breaker") 744 breaker.Reset() 745 timerActive = false 746 }(newSpec.CircuitBreaker.ReturnToServiceAfter, breakerPtr) 747 timerActive = true 748 } 749 750 if spec.Proxy.ServiceDiscovery.UseDiscoveryService { 751 if ServiceCache != nil { 752 log.Warning("[PROXY] [CIRCUIT BREAKER] Refreshing host list") 753 ServiceCache.Delete(spec.APIID) 754 } 755 } 756 757 spec.FireEvent(EventBreakerTriggered, EventCurcuitBreakerMeta{ 758 EventMetaDefault: EventMetaDefault{Message: "Breaker Tripped"}, 759 CircuitEvent: e, 760 Path: path, 761 APIID: spec.APIID, 762 }) 763 764 case circuit.BreakerReset: 765 // check if this spec is set to release resources 766 if spec.shouldRelease { 767 // time to stop this Go-routine 768 return 769 } 770 771 spec.FireEvent(EventBreakerTriggered, EventCurcuitBreakerMeta{ 772 EventMetaDefault: EventMetaDefault{Message: "Breaker Reset"}, 773 CircuitEvent: e, 774 Path: path, 775 APIID: spec.APIID, 776 }) 777 778 } 779 } 780 }(stringSpec.Path, apiSpec, newSpec.CircuitBreaker.CB) 781 782 urlSpec = append(urlSpec, newSpec) 783 } 784 785 return urlSpec 786 } 787 788 func (a APIDefinitionLoader) compileURLRewritesPathSpec(paths []apidef.URLRewriteMeta, stat URLStatus) []URLSpec { 789 // transform an extended configuration URL into an array of URLSpecs 790 // This way we can iterate the whole array once, on match we break with status 791 urlSpec := []URLSpec{} 792 793 for _, stringSpec := range paths { 794 curStringSpec := stringSpec 795 newSpec := URLSpec{} 796 a.generateRegex(curStringSpec.Path, &newSpec, stat) 797 // Extend with method actions 798 newSpec.URLRewrite = &curStringSpec 799 800 urlSpec = append(urlSpec, newSpec) 801 } 802 803 return urlSpec 804 } 805 806 func (a APIDefinitionLoader) compileVirtualPathspathSpec(paths []apidef.VirtualMeta, stat URLStatus, apiSpec *APISpec) []URLSpec { 807 if !config.Global().EnableJSVM { 808 return nil 809 } 810 811 // transform an extended configuration URL into an array of URLSpecs 812 // This way we can iterate the whole array once, on match we break with status 813 urlSpec := []URLSpec{} 814 for _, stringSpec := range paths { 815 newSpec := URLSpec{} 816 a.generateRegex(stringSpec.Path, &newSpec, stat) 817 // Extend with method actions 818 newSpec.VirtualPathSpec = stringSpec 819 820 preLoadVirtualMetaCode(&newSpec.VirtualPathSpec, &apiSpec.JSVM) 821 822 urlSpec = append(urlSpec, newSpec) 823 } 824 825 return urlSpec 826 } 827 828 func (a APIDefinitionLoader) compileTrackedEndpointPathspathSpec(paths []apidef.TrackEndpointMeta, stat URLStatus) []URLSpec { 829 urlSpec := []URLSpec{} 830 831 for _, stringSpec := range paths { 832 newSpec := URLSpec{} 833 a.generateRegex(stringSpec.Path, &newSpec, stat) 834 835 // set Path if it wasn't set 836 if stringSpec.Path == "" { 837 // even if it is empty (and regex matches everything) some middlewares expect to be value here 838 stringSpec.Path = "/" 839 } 840 841 // Extend with method actions 842 newSpec.TrackEndpoint = stringSpec 843 urlSpec = append(urlSpec, newSpec) 844 } 845 846 return urlSpec 847 } 848 849 func (a APIDefinitionLoader) compileValidateJSONPathspathSpec(paths []apidef.ValidatePathMeta, stat URLStatus) []URLSpec { 850 urlSpec := make([]URLSpec, len(paths)) 851 852 for i, stringSpec := range paths { 853 newSpec := URLSpec{} 854 a.generateRegex(stringSpec.Path, &newSpec, stat) 855 // Extend with method actions 856 857 stringSpec.SchemaCache = gojsonschema.NewGoLoader(stringSpec.Schema) 858 newSpec.ValidatePathMeta = stringSpec 859 urlSpec[i] = newSpec 860 } 861 862 return urlSpec 863 } 864 865 func (a APIDefinitionLoader) compileUnTrackedEndpointPathspathSpec(paths []apidef.TrackEndpointMeta, stat URLStatus) []URLSpec { 866 urlSpec := []URLSpec{} 867 868 for _, stringSpec := range paths { 869 newSpec := URLSpec{} 870 a.generateRegex(stringSpec.Path, &newSpec, stat) 871 // Extend with method actions 872 newSpec.DoNotTrackEndpoint = stringSpec 873 urlSpec = append(urlSpec, newSpec) 874 } 875 876 return urlSpec 877 } 878 879 func (a APIDefinitionLoader) compileInternalPathspathSpec(paths []apidef.InternalMeta, stat URLStatus) []URLSpec { 880 urlSpec := []URLSpec{} 881 882 for _, stringSpec := range paths { 883 newSpec := URLSpec{} 884 a.generateRegex(stringSpec.Path, &newSpec, stat) 885 // Extend with method actions 886 newSpec.Internal = stringSpec 887 urlSpec = append(urlSpec, newSpec) 888 } 889 890 return urlSpec 891 } 892 893 func (a APIDefinitionLoader) getExtendedPathSpecs(apiVersionDef apidef.VersionInfo, apiSpec *APISpec) ([]URLSpec, bool) { 894 // TODO: New compiler here, needs to put data into a different structure 895 896 ignoredPaths := a.compileExtendedPathSpec(apiVersionDef.ExtendedPaths.Ignored, Ignored) 897 blackListPaths := a.compileExtendedPathSpec(apiVersionDef.ExtendedPaths.BlackList, BlackList) 898 whiteListPaths := a.compileExtendedPathSpec(apiVersionDef.ExtendedPaths.WhiteList, WhiteList) 899 cachedPaths := a.compileCachedPathSpec(apiVersionDef.ExtendedPaths.Cached, apiVersionDef.ExtendedPaths.AdvanceCacheConfig) 900 transformPaths := a.compileTransformPathSpec(apiVersionDef.ExtendedPaths.Transform, Transformed) 901 transformResponsePaths := a.compileTransformPathSpec(apiVersionDef.ExtendedPaths.TransformResponse, TransformedResponse) 902 transformJQPaths := a.compileTransformJQPathSpec(apiVersionDef.ExtendedPaths.TransformJQ, TransformedJQ) 903 transformJQResponsePaths := a.compileTransformJQPathSpec(apiVersionDef.ExtendedPaths.TransformJQResponse, TransformedJQResponse) 904 headerTransformPaths := a.compileInjectedHeaderSpec(apiVersionDef.ExtendedPaths.TransformHeader, HeaderInjected) 905 headerTransformPathsOnResponse := a.compileInjectedHeaderSpec(apiVersionDef.ExtendedPaths.TransformResponseHeader, HeaderInjectedResponse) 906 hardTimeouts := a.compileTimeoutPathSpec(apiVersionDef.ExtendedPaths.HardTimeouts, HardTimeout) 907 circuitBreakers := a.compileCircuitBreakerPathSpec(apiVersionDef.ExtendedPaths.CircuitBreaker, CircuitBreaker, apiSpec) 908 urlRewrites := a.compileURLRewritesPathSpec(apiVersionDef.ExtendedPaths.URLRewrite, URLRewrite) 909 virtualPaths := a.compileVirtualPathspathSpec(apiVersionDef.ExtendedPaths.Virtual, VirtualPath, apiSpec) 910 requestSizes := a.compileRequestSizePathSpec(apiVersionDef.ExtendedPaths.SizeLimit, RequestSizeLimit) 911 methodTransforms := a.compileMethodTransformSpec(apiVersionDef.ExtendedPaths.MethodTransforms, MethodTransformed) 912 trackedPaths := a.compileTrackedEndpointPathspathSpec(apiVersionDef.ExtendedPaths.TrackEndpoints, RequestTracked) 913 unTrackedPaths := a.compileUnTrackedEndpointPathspathSpec(apiVersionDef.ExtendedPaths.DoNotTrackEndpoints, RequestNotTracked) 914 validateJSON := a.compileValidateJSONPathspathSpec(apiVersionDef.ExtendedPaths.ValidateJSON, ValidateJSONRequest) 915 internalPaths := a.compileInternalPathspathSpec(apiVersionDef.ExtendedPaths.Internal, Internal) 916 917 combinedPath := []URLSpec{} 918 combinedPath = append(combinedPath, ignoredPaths...) 919 combinedPath = append(combinedPath, blackListPaths...) 920 combinedPath = append(combinedPath, whiteListPaths...) 921 combinedPath = append(combinedPath, cachedPaths...) 922 combinedPath = append(combinedPath, transformPaths...) 923 combinedPath = append(combinedPath, transformResponsePaths...) 924 combinedPath = append(combinedPath, transformJQPaths...) 925 combinedPath = append(combinedPath, transformJQResponsePaths...) 926 combinedPath = append(combinedPath, headerTransformPaths...) 927 combinedPath = append(combinedPath, headerTransformPathsOnResponse...) 928 combinedPath = append(combinedPath, hardTimeouts...) 929 combinedPath = append(combinedPath, circuitBreakers...) 930 combinedPath = append(combinedPath, urlRewrites...) 931 combinedPath = append(combinedPath, requestSizes...) 932 combinedPath = append(combinedPath, virtualPaths...) 933 combinedPath = append(combinedPath, methodTransforms...) 934 combinedPath = append(combinedPath, trackedPaths...) 935 combinedPath = append(combinedPath, unTrackedPaths...) 936 combinedPath = append(combinedPath, validateJSON...) 937 combinedPath = append(combinedPath, internalPaths...) 938 939 return combinedPath, len(whiteListPaths) > 0 940 } 941 942 func (a *APISpec) Init(authStore, sessionStore, healthStore, orgStore storage.Handler) { 943 a.AuthManager.Init(authStore) 944 a.SessionManager.Init(sessionStore) 945 a.Health.Init(healthStore) 946 a.OrgSessionManager.Init(orgStore) 947 } 948 949 func (a *APISpec) StopSessionManagerPool() { 950 a.SessionManager.Stop() 951 a.OrgSessionManager.Stop() 952 } 953 954 func (a *APISpec) getURLStatus(stat URLStatus) RequestStatus { 955 switch stat { 956 case Ignored: 957 return StatusOkAndIgnore 958 case BlackList: 959 return EndPointNotAllowed 960 case WhiteList: 961 return StatusOk 962 case Cached: 963 return StatusCached 964 case Transformed: 965 return StatusTransform 966 case TransformedJQ: 967 return StatusTransformJQ 968 case HeaderInjected: 969 return StatusHeaderInjected 970 case HeaderInjectedResponse: 971 return StatusHeaderInjectedResponse 972 case TransformedResponse: 973 return StatusTransformResponse 974 case TransformedJQResponse: 975 return StatusTransformJQResponse 976 case HardTimeout: 977 return StatusHardTimeout 978 case CircuitBreaker: 979 return StatusCircuitBreaker 980 case URLRewrite: 981 return StatusURLRewrite 982 case VirtualPath: 983 return StatusVirtualPath 984 case RequestSizeLimit: 985 return StatusRequestSizeControlled 986 case MethodTransformed: 987 return StatusMethodTransformed 988 case RequestTracked: 989 return StatusRequestTracked 990 case RequestNotTracked: 991 return StatusRequestNotTracked 992 case ValidateJSONRequest: 993 return StatusValidateJSON 994 case Internal: 995 return StatusInternal 996 997 default: 998 log.Error("URL Status was not one of Ignored, Blacklist or WhiteList! Blocking.") 999 return EndPointNotAllowed 1000 } 1001 } 1002 1003 // URLAllowedAndIgnored checks if a url is allowed and ignored. 1004 func (a *APISpec) URLAllowedAndIgnored(r *http.Request, rxPaths []URLSpec, whiteListStatus bool) (RequestStatus, interface{}) { 1005 // Check if ignored 1006 for _, v := range rxPaths { 1007 if !v.Spec.MatchString(r.URL.Path) { 1008 continue 1009 } 1010 1011 if v.MethodActions != nil { 1012 // We are using an extended path set, check for the method 1013 methodMeta, matchMethodOk := v.MethodActions[r.Method] 1014 if !matchMethodOk { 1015 continue 1016 } 1017 1018 // Matched the method, check what status it is 1019 // TODO: Extend here for additional reply options 1020 switch methodMeta.Action { 1021 case apidef.NoAction: 1022 // NoAction status means we're not treating this request in any special or exceptional way 1023 return a.getURLStatus(v.Status), nil 1024 case apidef.Reply: 1025 return StatusRedirectFlowByReply, &methodMeta 1026 default: 1027 log.Error("URL Method Action was not set to NoAction, blocking.") 1028 return EndPointNotAllowed, nil 1029 } 1030 } 1031 1032 if r.Method == v.Internal.Method && v.Status == Internal && !ctxLoopingEnabled(r) { 1033 return EndPointNotAllowed, nil 1034 } 1035 1036 if whiteListStatus { 1037 // We have a whitelist, nothing gets through unless specifically defined 1038 switch v.Status { 1039 case WhiteList, BlackList, Ignored: 1040 default: 1041 if v.Status == Internal && r.Method == v.Internal.Method && ctxLoopingEnabled(r) { 1042 return a.getURLStatus(v.Status), nil 1043 } else { 1044 return EndPointNotAllowed, nil 1045 } 1046 } 1047 } 1048 1049 if v.TransformAction.Template != nil { 1050 return a.getURLStatus(v.Status), &v.TransformAction 1051 } 1052 1053 if v.TransformJQAction.Filter != "" { 1054 return a.getURLStatus(v.Status), &v.TransformJQAction 1055 } 1056 1057 // TODO: Fix, Not a great detection method 1058 if len(v.InjectHeaders.Path) > 0 { 1059 return a.getURLStatus(v.Status), &v.InjectHeaders 1060 } 1061 1062 // Using a legacy path, handle it raw. 1063 return a.getURLStatus(v.Status), nil 1064 } 1065 1066 // Nothing matched - should we still let it through? 1067 if whiteListStatus { 1068 // We have a whitelist, nothing gets through unless specifically defined 1069 return EndPointNotAllowed, nil 1070 } 1071 1072 // No whitelist, but also not in any of the other lists, let it through and filter 1073 return StatusOk, nil 1074 } 1075 1076 // CheckSpecMatchesStatus checks if a url spec has a specific status 1077 func (a *APISpec) CheckSpecMatchesStatus(r *http.Request, rxPaths []URLSpec, mode URLStatus) (bool, interface{}) { 1078 var matchPath, method string 1079 1080 //If url-rewrite middleware was used, call response middleware of original path and not of rewritten path 1081 // context variable UrlRewritePath is set by rewrite middleware 1082 if mode == TransformedJQResponse || mode == HeaderInjectedResponse || mode == TransformedResponse { 1083 matchPath = ctxGetUrlRewritePath(r) 1084 method = ctxGetRequestMethod(r) 1085 if matchPath == "" { 1086 matchPath = r.URL.Path 1087 } 1088 } else { 1089 matchPath = r.URL.Path 1090 method = r.Method 1091 } 1092 1093 if a.Proxy.ListenPath != "/" { 1094 matchPath = strings.TrimPrefix(matchPath, a.Proxy.ListenPath) 1095 } 1096 1097 if !strings.HasPrefix(matchPath, "/") { 1098 matchPath = "/" + matchPath 1099 } 1100 1101 // Check if ignored 1102 for _, v := range rxPaths { 1103 if mode != v.Status { 1104 continue 1105 } 1106 if !v.Spec.MatchString(matchPath) { 1107 continue 1108 } 1109 1110 switch v.Status { 1111 case Ignored, BlackList, WhiteList: 1112 return true, nil 1113 case Cached: 1114 if method == v.CacheConfig.Method || (v.CacheConfig.Method == SAFE_METHODS && (method == "GET" || method == "HEADERS" || method == "OPTIONS")) { 1115 return true, &v.CacheConfig 1116 } 1117 case Transformed: 1118 if method == v.TransformAction.Method { 1119 return true, &v.TransformAction 1120 } 1121 case TransformedJQ: 1122 if method == v.TransformJQAction.Method { 1123 return true, &v.TransformJQAction 1124 } 1125 case HeaderInjected: 1126 if method == v.InjectHeaders.Method { 1127 return true, &v.InjectHeaders 1128 } 1129 case HeaderInjectedResponse: 1130 if method == v.InjectHeadersResponse.Method { 1131 return true, &v.InjectHeadersResponse 1132 } 1133 case TransformedResponse: 1134 if method == v.TransformResponseAction.Method { 1135 return true, &v.TransformResponseAction 1136 } 1137 case TransformedJQResponse: 1138 if method == v.TransformJQResponseAction.Method { 1139 return true, &v.TransformJQResponseAction 1140 } 1141 case HardTimeout: 1142 if r.Method == v.HardTimeout.Method { 1143 return true, &v.HardTimeout.TimeOut 1144 } 1145 case CircuitBreaker: 1146 if method == v.CircuitBreaker.Method { 1147 return true, &v.CircuitBreaker 1148 } 1149 case URLRewrite: 1150 if method == v.URLRewrite.Method { 1151 return true, v.URLRewrite 1152 } 1153 case VirtualPath: 1154 if method == v.VirtualPathSpec.Method { 1155 return true, &v.VirtualPathSpec 1156 } 1157 case RequestSizeLimit: 1158 if method == v.RequestSize.Method { 1159 return true, &v.RequestSize 1160 } 1161 case MethodTransformed: 1162 if method == v.MethodTransform.Method { 1163 return true, &v.MethodTransform 1164 } 1165 case RequestTracked: 1166 if method == v.TrackEndpoint.Method { 1167 return true, &v.TrackEndpoint 1168 } 1169 case RequestNotTracked: 1170 if method == v.DoNotTrackEndpoint.Method { 1171 return true, &v.DoNotTrackEndpoint 1172 } 1173 case ValidateJSONRequest: 1174 if method == v.ValidatePathMeta.Method { 1175 return true, &v.ValidatePathMeta 1176 } 1177 case Internal: 1178 if method == v.Internal.Method { 1179 return true, &v.Internal 1180 } 1181 } 1182 } 1183 return false, nil 1184 } 1185 1186 func (a *APISpec) getVersionFromRequest(r *http.Request) string { 1187 if a.VersionData.NotVersioned { 1188 return "" 1189 } 1190 1191 switch a.VersionDefinition.Location { 1192 case headerLocation: 1193 return r.Header.Get(a.VersionDefinition.Key) 1194 1195 case urlParamLocation: 1196 return r.URL.Query().Get(a.VersionDefinition.Key) 1197 1198 case urlLocation: 1199 uPath := strings.TrimPrefix(r.URL.Path, a.Proxy.ListenPath) 1200 uPath = strings.TrimPrefix(uPath, "/"+a.Slug) 1201 1202 // First non-empty part of the path is the version ID 1203 for _, part := range strings.Split(uPath, "/") { 1204 if part != "" { 1205 return part 1206 } 1207 } 1208 } 1209 return "" 1210 } 1211 1212 // VersionExpired checks if an API version (during a proxied 1213 // request) is expired. If it isn't and the configured time was valid, 1214 // it also returns the expiration time. 1215 func (a *APISpec) VersionExpired(versionDef *apidef.VersionInfo) (bool, *time.Time) { 1216 if a.VersionData.NotVersioned { 1217 return false, nil 1218 } 1219 1220 // Never expires 1221 if versionDef.Expires == "" || versionDef.Expires == "-1" { 1222 return false, nil 1223 } 1224 1225 // otherwise use parsed timestamp 1226 if versionDef.ExpiresTs.IsZero() { 1227 log.Error("Could not parse expiry date for API, disallow") 1228 return true, nil 1229 } 1230 1231 // It's in the past, expire 1232 // It's in the future, keep going 1233 return time.Since(versionDef.ExpiresTs) >= 0, &versionDef.ExpiresTs 1234 } 1235 1236 // RequestValid will check if an incoming request has valid version 1237 // data and return a RequestStatus that describes the status of the 1238 // request 1239 func (a *APISpec) RequestValid(r *http.Request) (bool, RequestStatus, interface{}) { 1240 versionMetaData, versionPaths, whiteListStatus, vstat := a.Version(r) 1241 1242 // Screwed up version info - fail and pass through 1243 if vstat != StatusOk { 1244 return false, vstat, nil 1245 } 1246 1247 // Is the API version expired? 1248 // TODO: Don't abuse the interface{} return value for both 1249 // *apidef.EndpointMethodMeta and *time.Time. Probably need to 1250 // redesign or entirely remove RequestValid. See discussion on 1251 // https://github.com/TykTechnologies/tyk/pull/776 1252 expired, expTime := a.VersionExpired(versionMetaData) 1253 if expired { 1254 return false, VersionExpired, nil 1255 } 1256 1257 // not expired, let's check path info 1258 status, meta := a.URLAllowedAndIgnored(r, versionPaths, whiteListStatus) 1259 switch status { 1260 case EndPointNotAllowed: 1261 return false, status, expTime 1262 case StatusRedirectFlowByReply: 1263 return true, status, meta 1264 case StatusOkAndIgnore, StatusCached, StatusTransform, 1265 StatusHeaderInjected, StatusMethodTransformed: 1266 return true, status, expTime 1267 default: 1268 return true, StatusOk, expTime 1269 } 1270 } 1271 1272 // Version attempts to extract the version data from a request, depending on where it is stored in the 1273 // request (currently only "header" is supported) 1274 func (a *APISpec) Version(r *http.Request) (*apidef.VersionInfo, []URLSpec, bool, RequestStatus) { 1275 var version apidef.VersionInfo 1276 1277 // try the context first 1278 if v := ctxGetVersionInfo(r); v != nil { 1279 version = *v 1280 } else { 1281 // Are we versioned? 1282 if a.VersionData.NotVersioned { 1283 // Get the first one in the list 1284 for _, v := range a.VersionData.Versions { 1285 version = v 1286 break 1287 } 1288 } else { 1289 // Extract Version Info 1290 // First checking for if default version is set 1291 vName := a.getVersionFromRequest(r) 1292 if vName == "" { 1293 if a.VersionData.DefaultVersion == "" { 1294 return &version, nil, false, VersionNotFound 1295 } 1296 vName = a.VersionData.DefaultVersion 1297 ctxSetDefaultVersion(r) 1298 } 1299 // Load Version Data - General 1300 var ok bool 1301 if version, ok = a.VersionData.Versions[vName]; !ok { 1302 return &version, nil, false, VersionDoesNotExist 1303 } 1304 } 1305 1306 // cache for the future 1307 ctxSetVersionInfo(r, &version) 1308 } 1309 1310 // Load path data and whitelist data for version 1311 rxPaths, rxOk := a.RxPaths[version.Name] 1312 if !rxOk { 1313 log.Error("no RX Paths found for version ", version.Name) 1314 return &version, nil, false, VersionDoesNotExist 1315 } 1316 1317 whiteListStatus, wlOk := a.WhiteListEnabled[version.Name] 1318 if !wlOk { 1319 log.Error("No whitelist data found") 1320 return &version, nil, false, VersionWhiteListStatusNotFound 1321 } 1322 1323 return &version, rxPaths, whiteListStatus, StatusOk 1324 } 1325 1326 type RoundRobin struct { 1327 pos uint32 1328 } 1329 1330 func (r *RoundRobin) WithLen(len int) int { 1331 if len < 1 { 1332 return 0 1333 } 1334 // -1 to start at 0, not 1 1335 cur := atomic.AddUint32(&r.pos, 1) - 1 1336 return int(cur) % len 1337 }