github.com/goravel/framework@v1.13.9/http/middleware/throttle.go (about)

     1  package middleware
     2  
     3  import (
     4  	"crypto/md5"
     5  	"encoding/hex"
     6  	"fmt"
     7  	"strconv"
     8  	"time"
     9  
    10  	httpcontract "github.com/goravel/framework/contracts/http"
    11  	"github.com/goravel/framework/http"
    12  	httplimit "github.com/goravel/framework/http/limit"
    13  	"github.com/goravel/framework/support/carbon"
    14  )
    15  
    16  func Throttle(name string) httpcontract.Middleware {
    17  	return func(ctx httpcontract.Context) {
    18  		if limiter := http.RateLimiterFacade.Limiter(name); limiter != nil {
    19  			if limits := limiter(ctx); len(limits) > 0 {
    20  				for _, limit := range limits {
    21  					if instance, ok := limit.(*httplimit.Limit); ok {
    22  						key, timer := key(ctx, name, instance)
    23  						currentTimes := 1
    24  
    25  						if http.CacheFacade.Has(timer) {
    26  							value := http.CacheFacade.GetInt(key, 0)
    27  							if value >= instance.MaxAttempts {
    28  								expireSecond := http.CacheFacade.GetInt(timer, 0) + instance.DecayMinutes*60
    29  								ctx.Response().Header("X-RateLimit-Reset", strconv.Itoa(expireSecond))
    30  								ctx.Response().Header("Retry-After", strconv.Itoa(expireSecond-int(carbon.Now().Timestamp())))
    31  								if instance.ResponseCallback != nil {
    32  									instance.ResponseCallback(ctx)
    33  									return
    34  								} else {
    35  									ctx.Request().AbortWithStatus(httpcontract.StatusTooManyRequests)
    36  									return
    37  								}
    38  							} else {
    39  								var err error
    40  								if currentTimes, err = http.CacheFacade.Increment(key); err != nil {
    41  									panic(err)
    42  								}
    43  							}
    44  						} else {
    45  							expireMinute := time.Duration(instance.DecayMinutes) * time.Minute
    46  
    47  							err := http.CacheFacade.Put(timer, carbon.Now().Timestamp(), expireMinute)
    48  							if err != nil {
    49  								panic(err)
    50  							}
    51  
    52  							err = http.CacheFacade.Put(key, currentTimes, expireMinute)
    53  							if err != nil {
    54  								panic(err)
    55  							}
    56  						}
    57  
    58  						// add the headers for the passed request
    59  						ctx.Response().Header("X-RateLimit-Limit", strconv.Itoa(instance.MaxAttempts))
    60  						ctx.Response().Header("X-RateLimit-Remaining", strconv.Itoa(instance.MaxAttempts-currentTimes))
    61  					}
    62  				}
    63  			}
    64  		}
    65  
    66  		ctx.Request().Next()
    67  	}
    68  }
    69  
    70  func key(ctx httpcontract.Context, limiter string, limit *httplimit.Limit) (string, string) {
    71  	// if no key is set, use the path and ip address as the default key
    72  	var key, timer string
    73  	prefix := http.ConfigFacade.GetString("cache.prefix")
    74  	if len(limit.Key) == 0 {
    75  		hash := md5.Sum([]byte(ctx.Request().Path()))
    76  		key = fmt.Sprintf("%s:throttle:%s:%s:%s", prefix, limiter, hex.EncodeToString(hash[:]), ctx.Request().Ip())
    77  	} else {
    78  		hash := md5.Sum([]byte(limit.Key))
    79  		key = fmt.Sprintf("%s:throttle:%s:%s", prefix, limiter, hex.EncodeToString(hash[:]))
    80  	}
    81  	timer = key + ":timer"
    82  
    83  	return key, timer
    84  }