github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/mw_rate_limiting.go (about)

     1  package gateway
     2  
     3  import (
     4  	"errors"
     5  	"net/http"
     6  	"time"
     7  
     8  	"github.com/sirupsen/logrus"
     9  
    10  	"github.com/TykTechnologies/tyk/request"
    11  )
    12  
    13  var sessionLimiter = SessionLimiter{}
    14  var sessionMonitor = Monitor{}
    15  
    16  // RateLimitAndQuotaCheck will check the incomming request and key whether it is within it's quota and
    17  // within it's rate limit, it makes use of the SessionLimiter object to do this
    18  type RateLimitAndQuotaCheck struct {
    19  	BaseMiddleware
    20  }
    21  
    22  func (k *RateLimitAndQuotaCheck) Name() string {
    23  	return "RateLimitAndQuotaCheck"
    24  }
    25  
    26  func (k *RateLimitAndQuotaCheck) EnabledForSpec() bool {
    27  	return !k.Spec.DisableRateLimit || !k.Spec.DisableQuota
    28  }
    29  
    30  func (k *RateLimitAndQuotaCheck) handleRateLimitFailure(r *http.Request, token string) (error, int) {
    31  	k.Logger().WithField("key", obfuscateKey(token)).Info("Key rate limit exceeded.")
    32  
    33  	// Fire a rate limit exceeded event
    34  	k.FireEvent(EventRateLimitExceeded, EventKeyFailureMeta{
    35  		EventMetaDefault: EventMetaDefault{Message: "Key Rate Limit Exceeded", OriginatingRequest: EncodeRequestToEvent(r)},
    36  		Path:             r.URL.Path,
    37  		Origin:           request.RealIP(r),
    38  		Key:              token,
    39  	})
    40  
    41  	// Report in health check
    42  	reportHealthValue(k.Spec, Throttle, "-1")
    43  
    44  	return errors.New("Rate limit exceeded"), http.StatusTooManyRequests
    45  }
    46  
    47  func (k *RateLimitAndQuotaCheck) handleQuotaFailure(r *http.Request, token string) (error, int) {
    48  	k.Logger().WithField("key", obfuscateKey(token)).Info("Key quota limit exceeded.")
    49  
    50  	// Fire a quota exceeded event
    51  	k.FireEvent(EventQuotaExceeded, EventKeyFailureMeta{
    52  		EventMetaDefault: EventMetaDefault{Message: "Key Quota Limit Exceeded", OriginatingRequest: EncodeRequestToEvent(r)},
    53  		Path:             r.URL.Path,
    54  		Origin:           request.RealIP(r),
    55  		Key:              token,
    56  	})
    57  
    58  	// Report in health check
    59  	reportHealthValue(k.Spec, QuotaViolation, "-1")
    60  
    61  	return errors.New("Quota exceeded"), http.StatusForbidden
    62  }
    63  
    64  // ProcessRequest will run any checks on the request on the way through the system, return an error to have the chain fail
    65  func (k *RateLimitAndQuotaCheck) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
    66  	if ctxGetRequestStatus(r) == StatusOkAndIgnore {
    67  		return nil, http.StatusOK
    68  	}
    69  
    70  	// Skip rate limiting and quotas for looping
    71  	if !ctxCheckLimits(r) {
    72  		return nil, http.StatusOK
    73  	}
    74  
    75  	session := ctxGetSession(r)
    76  	token := ctxGetAuthToken(r)
    77  
    78  	storeRef := k.Spec.SessionManager.Store()
    79  	reason := sessionLimiter.ForwardMessage(
    80  		r,
    81  		session,
    82  		token,
    83  		storeRef,
    84  		!k.Spec.DisableRateLimit,
    85  		!k.Spec.DisableQuota,
    86  		&k.Spec.GlobalConfig,
    87  		k.Spec.APIID,
    88  		false,
    89  	)
    90  
    91  	throttleRetryLimit := session.ThrottleRetryLimit
    92  	throttleInterval := session.ThrottleInterval
    93  
    94  	if len(session.AccessRights) > 0 {
    95  		if rights, ok := session.GetAccessRightByAPIID(k.Spec.APIID); ok {
    96  			if rights.Limit != nil {
    97  				throttleInterval = rights.Limit.ThrottleInterval
    98  				throttleRetryLimit = rights.Limit.ThrottleRetryLimit
    99  			}
   100  		}
   101  	}
   102  
   103  	switch reason {
   104  	case sessionFailNone:
   105  	case sessionFailRateLimit:
   106  		err, errCode := k.handleRateLimitFailure(r, token)
   107  		if throttleRetryLimit > 0 {
   108  			for {
   109  				ctxIncThrottleLevel(r, throttleRetryLimit)
   110  				time.Sleep(time.Duration(throttleInterval * float64(time.Second)))
   111  
   112  				reason = sessionLimiter.ForwardMessage(
   113  					r,
   114  					session,
   115  					token,
   116  					storeRef,
   117  					!k.Spec.DisableRateLimit,
   118  					!k.Spec.DisableQuota,
   119  					&k.Spec.GlobalConfig,
   120  					k.Spec.APIID,
   121  					true,
   122  				)
   123  
   124  				log.WithFields(logrus.Fields{
   125  					"middleware": "RateLimitAndQuotaCheck",
   126  					"func":       "ProcessRequest",
   127  				}).Debugf("after dry-run (reason: '%s')", reason)
   128  
   129  				if ctxThrottleLevel(r) > throttleRetryLimit {
   130  					break
   131  				}
   132  
   133  				if reason == sessionFailNone {
   134  					return k.ProcessRequest(w, r, nil)
   135  				}
   136  			}
   137  		}
   138  		return err, errCode
   139  
   140  	case sessionFailQuota:
   141  		return k.handleQuotaFailure(r, token)
   142  	default:
   143  		// Other reason? Still not allowed
   144  		return errors.New("Access denied"), http.StatusForbidden
   145  	}
   146  	// Run the trigger monitor
   147  	if k.Spec.GlobalConfig.Monitor.MonitorUserKeys {
   148  		sessionMonitor.Check(session, token)
   149  	}
   150  
   151  	// Request is valid, carry on
   152  	return nil, http.StatusOK
   153  }