github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/http/fast/middleware/limiter/ratelimiter.go (about)

     1  package limiter
     2  
     3  import (
     4  	"errors"
     5  	"github.com/angenalZZZ/gofunc/f"
     6  	"github.com/angenalZZZ/gofunc/http/fast"
     7  	"github.com/valyala/fasthttp"
     8  	"strconv"
     9  	"time"
    10  )
    11  
    12  // ErrTooManyRequests is returned when too many requests.
    13  var ErrTooManyRequests = errors.New("too many requests")
    14  
    15  // ErrRateLimitHeader response when any requests.
    16  type ErrRateLimitHeader struct {
    17  	Allowed bool
    18  	Header  RateLimitHeader
    19  }
    20  type RateLimitHeader struct {
    21  	Limit, Remaining, Reset, RetryAfter int64
    22  }
    23  
    24  // ResponseRateLimitHeader response when any requests.
    25  func ResponseRateLimitHeader(c *fast.Ctx, opt *ErrRateLimitHeader) {
    26  	//c.SetHeader("X-Rate-Limit-Duration", "1")
    27  	if opt.Allowed {
    28  		c.SetHeader("X-RateLimit-Limit", strconv.FormatInt(opt.Header.Limit, 10))
    29  		c.SetHeader("X-RateLimit-Remaining", strconv.FormatInt(opt.Header.Remaining, 10))
    30  		c.SetHeader("X-RateLimit-Reset", strconv.FormatInt(opt.Header.Reset, 10))
    31  	} else {
    32  		// Return response with Retry-After header
    33  		// https://tools.ietf.org/html/rfc6584
    34  		c.SetHeader("Retry-After", strconv.FormatInt(opt.Header.RetryAfter, 10))
    35  	}
    36  }
    37  
    38  // Config defines the config for limiter middleware
    39  type Config struct {
    40  	// Filter defines a function to skip middleware.
    41  	// Optional. Default: nil
    42  	Filter func(*fast.Ctx) bool
    43  	// Timeout in seconds on how long to keep records of requests in memory
    44  	// Default: 1
    45  	Timeout int64
    46  	// Max number of recent connections during `Timeout` seconds before sending a 429 response
    47  	// Default: 100
    48  	Max int64
    49  	// Message
    50  	// default: "Too many requests, please try again later."
    51  	Message string
    52  	// StatusCode
    53  	// Default: 429 Too Many Requests
    54  	StatusCode int
    55  	// Key allows to use a custom handler to create custom keys
    56  	// Default: func(c *fast.Ctx) string {
    57  	//   return c.IP()
    58  	// }
    59  	Key func(*fast.Ctx) string
    60  	// Handler is called when a request hits the limit
    61  	// Default: func(c *fast.Ctx) {
    62  	//   c.Status(cfg.StatusCode).SendString(cfg.Message)
    63  	// }
    64  	Handler func(*fast.Ctx)
    65  }
    66  
    67  // New middleware.
    68  //  cfg := limiter.Config{
    69  //    Max: 100,
    70  //  }
    71  // app.Use(limiter.New(cfg))
    72  func New(config ...Config) func(*fast.Ctx) {
    73  	// Init config
    74  	var cfg Config
    75  	if len(config) > 0 {
    76  		cfg = config[0]
    77  	}
    78  	if cfg.Timeout == 0 {
    79  		cfg.Timeout = 1
    80  	}
    81  	if cfg.Max == 0 {
    82  		cfg.Max = 100
    83  	}
    84  	if cfg.Message == "" {
    85  		cfg.Message = ErrTooManyRequests.Error()
    86  	}
    87  	if cfg.StatusCode == 0 {
    88  		cfg.StatusCode = fasthttp.StatusTooManyRequests
    89  	}
    90  	if cfg.Key == nil {
    91  		cfg.Key = func(c *fast.Ctx) string {
    92  			return c.IP()
    93  		}
    94  	}
    95  	if cfg.Handler == nil {
    96  		cfg.Handler = func(c *fast.Ctx) {
    97  			c.Status(cfg.StatusCode).SendString(cfg.Message)
    98  		}
    99  	}
   100  	// Limiter settings
   101  	var hits = f.NewConcurrentMap()
   102  	var reset = f.NewConcurrentMap()
   103  	var timestamp = time.Now().UnixNano()
   104  	// Update timestamp every second
   105  	go func() {
   106  		for {
   107  			timestamp = time.Now().UnixNano()
   108  			time.Sleep(time.Microsecond)
   109  		}
   110  	}()
   111  	// Reset hits every cfg.Timeout
   112  	go func() {
   113  		var zero int64
   114  		sleep := time.Duration(cfg.Timeout) * time.Second
   115  		for {
   116  			// For every key in reset
   117  			for item := range reset.IterBuffered() {
   118  				// If resetTime exist and current time is equal or bigger
   119  				i := item.Val.(int64)
   120  				if i != zero && timestamp >= i {
   121  					// Reset hits and resetTime
   122  					hits.Set(item.Key, zero)
   123  					reset.Set(item.Key, zero)
   124  				}
   125  			}
   126  			// Wait cfg.Timeout
   127  			time.Sleep(sleep)
   128  		}
   129  	}()
   130  	return func(c *fast.Ctx) {
   131  		// Filter request to skip middleware
   132  		if cfg.Filter != nil && cfg.Filter(c) {
   133  			c.Next()
   134  			return
   135  		}
   136  		// GetHeader key (default is the remote IP)
   137  		key := cfg.Key(c)
   138  		// SetHeader unix timestamp if not exist
   139  		var hitReset int64
   140  		if hit, ok := reset.Get(key); ok {
   141  			hitReset = hit.(int64)
   142  		} else {
   143  			hitReset = timestamp + cfg.Timeout
   144  			reset.Set(key, hitReset)
   145  		}
   146  		// Increment key hits
   147  		var hitCount int64
   148  		if hit, ok := hits.Get(key); ok && hitReset != 0 {
   149  			hitCount = hit.(int64) + 1
   150  		} else {
   151  			hitCount = 1
   152  		}
   153  		hits.Set(key, hitCount)
   154  		// SetHeader how many hits we have left
   155  		remaining := cfg.Max - hitCount
   156  		// Calculate when it resets in seconds
   157  		resetTime := hitReset - timestamp
   158  		// Check if hits exceed the cfg.Max
   159  		if remaining < 0 {
   160  			// Call Handler func
   161  			cfg.Handler(c)
   162  			// Return response with Retry-After header
   163  			ResponseRateLimitHeader(c, &ErrRateLimitHeader{Allowed: false, Header: RateLimitHeader{RetryAfter: resetTime}})
   164  			return
   165  		}
   166  		// We can continue, update RateLimit headers
   167  		ResponseRateLimitHeader(c, &ErrRateLimitHeader{
   168  			Allowed: true,
   169  			Header: RateLimitHeader{
   170  				Limit:     cfg.Max,
   171  				Remaining: remaining,
   172  				Reset:     resetTime,
   173  			},
   174  		})
   175  		c.Next()
   176  	}
   177  }