ratelimit

package module
v0.0.0-...-4e9cd30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2018 License: Apache-2.0 Imports: 19 Imported by: 0

README

krakend-ratelimit

Krankend rate limitir implementation based on GCRA algorithm using github.com/throttled/throttled implementation

Implementation

It's based on a middleware to in order to intercept request before reaching any endpoint

Configuration

The configuration should be added in the service extra_config.

Example
{
  "extra_config": {
    "github.com/schibsted/krakend-ratelimit": {
      "enabled": true,
      "default": {
        "max_requests": 600,
        "burst_size": 5
      },
      "custom": {
        "kufar.com": {
            "max_requests": 60,
             "burst_size": 10
         },
           "corotos.com": {
             "max_requests": 40,
             "burst_size": 5
         }
      }
    }
  },
  "version": 2,
  "max_idle_connections": 250,
  "timeout": "3000ms",
  "read_timeout": "0s",
  "write_timeout": "0s",
  "idle_timeout": "0s",
  "read_header_timeout": "0s",
  "name": "Test",
  "endpoints": [
    {
      "endpoint": "/hello",
      "method": "GET",
      "backend": [
        {
          "url_pattern": "/hello",
          "host": [
            "http://localhost:8000"
          ]
        }
      ],
      "timeout": "1500ms",
      "max_rate": "10000"
    }
  ]
}

We can override default rate limit settings for specific sites. We use the issuer value stored in JWT token as rate-limiter key.

Building and using the rate limiter:

See an usage example here

func ApiGateway() {
	logger, err := logging.NewLogger("INFO", os.Stdout, "[KRAKEND]")
	if err != nil {
		panic(err)
	}

	parser := config.NewParser()
	serviceConfig, err := parser.Parse("./test.json")
	if err != nil {
		panic(err)
	}

  rateLimitCfg := ConfigGetter(serviceConfig.ExtraConfig).(RateLimitConfig)
  nodeCounter :=  DefaultNodeCounter()
	rateLimiter, err := GinRateLimit(rateLimitCfg, nodeCounter), logger)
	if err != nil {
		panic(err)
	}

  contextRateLimiter := GinContextRateLimit(rateLimiter, "SiteKey")
  middleware := contextRateLimiter.RateLimit()
	middlewares := []gin.HandlerFunc{ginMiddleware}

	routerFactory := kgin.NewFactory(
		kgin.Config{
			Engine:         gin.Default(),
			Middlewares:    middlewares,
			HandlerFactory: kgin.EndpointHandler,
			ProxyFactory:   proxy.DefaultFactory(logger),
			Logger:         logger,
		},
	)

	routerFactory.New().Run(serviceConfig)
}
nodeCounter :=  DefaultNodeCounter()

The NodeCounter interface is used to determine what is the request imit per server based on the global limit. That is, if only one instance of the server is running, then NodeCounter should return 1. In consecuence the request server limit and the request global limit will be the same.

Imagine now, we have two servers. So NodeCounter should return 2. In that case, server request limit should be the half of the request global limit.

There's an existing implementation which looks for more AWS EC2 instances:

//Node counter for amazon EC2
func NewAwsNodeCounter(EC2 *EC2, autoScaling *AutoScaling,
	iid *ec2metadata.EC2InstanceIdentityDocument, logger logging.Logger) NodeCounter {

Now, we build the RateLimitier with the node counter and the configuration

rateLimiter, err := GinRateLimit(rateLimitCfg, nodeCounter), logger)

Afterwards we create the GinRateLimiter with the GinRateLimit and we wirethe middleware with gin

contextRateLimiter := GinContextRateLimit(rateLimiter, "SiteKey")
middleware := contextRateLimiter.RateLimit()
middlewares := []gin.HandlerFunc{ginMiddleware}

There's two classes of rGinRateLimiter, per issuer or per ip:


// Build context key based rate limiter (e.g. we can store the issuer/tenant as a context param)
func GinContextRateLimit(rateLimiter throttled.RateLimiter, contextKey string) *GinRateLimiter {
	varyBy := readContextKey(contextKey)
	return &GinRateLimiter{
		RateLimiter: rateLimiter,
		VaryBy:      varyBy,
	}
}

// Build IP based rate limiter
func GinIpRateLimit(rateLimiter throttled.RateLimiter, contextKey string) *GinRateLimiter {
	return &GinRateLimiter{
		RateLimiter: rateLimiter,
		VaryBy:      getRequestIp,
	}
}

Documentation

Index

Constants

View Source
const Namespace = "github.com/schibsted/krakend-ratelimit"

Namespace is the key to look for extra configuration details

Variables

View Source
var (
	// DefaultDeniedHandler is the default DeniedHandler for an
	// HTTPRateLimiter. It returns a 429 status code with a generic
	// message.
	DefaultDeniedHandler = func(c *gin.Context) {
		c.JSON(http.StatusTooManyRequests, "limit exceeded")
		c.AbortWithStatus(http.StatusTooManyRequests)
	}

	// DefaultError is the default Error function for an HTTPRateLimiter.
	// It returns a 500 status code with a generic message.
	DefaultError = func(c *gin.Context, err error) {
		c.JSON(http.StatusInternalServerError, "internal error")
		c.AbortWithStatus(http.StatusInternalServerError)
	}
)

Functions

func ConfigGetter

func ConfigGetter(e config.ExtraConfig) interface{}

ConfigGetter implements the config.ConfigGetter interface

func NewAwsSession

func NewAwsSession() *session.Session

func NewAwsSessionWithRegion

func NewAwsSessionWithRegion(region string) *session.Session

func RateLimitUpdater

func RateLimitUpdater(rateLimiter UpdatableClusterRateLimiter, interval time.Duration, nodeCounter NodeCounter, logger logging.Logger)

Update internal GinRateLimit settings depending on service configuration on ApiGW nodes amount

Types

type AutoScaling

type AutoScaling struct {
	Client autoscalingiface.AutoScalingAPI
}

func NewAutoScaling

func NewAutoScaling(sess *session.Session) *AutoScaling

type ClusterAwareRateLimiter

type ClusterAwareRateLimiter struct {
	// contains filtered or unexported fields
}

Updatable RateLimiter. Cluster aware (track node count). Update node sttings depending on total node count

func (*ClusterAwareRateLimiter) Nodes

func (r *ClusterAwareRateLimiter) Nodes() int

func (*ClusterAwareRateLimiter) RateLimit

func (r *ClusterAwareRateLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error)

func (*ClusterAwareRateLimiter) Update

func (*ClusterAwareRateLimiter) UpdateNodeCount

func (r *ClusterAwareRateLimiter) UpdateNodeCount(nodes int) error

type DynamicRateLimiter

type DynamicRateLimiter struct {
	throttled.RateLimiter
	// contains filtered or unexported fields
}

Implements UpdatableRateLimiter, so, we can modify

rate-limit settings in execution time

func (*DynamicRateLimiter) Update

func (r *DynamicRateLimiter) Update(settings RateLimiterSettings) error

type EC2

type EC2 struct {
	Client ec2iface.EC2API
}

func NewEC2

func NewEC2(sess *session.Session) *EC2

func (*EC2) DescribeTags

func (c *EC2) DescribeTags(input *ec2.DescribeTagsInput) (*ec2.DescribeTagsOutput, error)

func (*EC2) GetAsgName

func (c *EC2) GetAsgName(instanceID string) (string, error)

type EC2Metadata

type EC2Metadata struct {
	Client ec2metadata.EC2Metadata
}

func NewEC2Metadata

func NewEC2Metadata(sess *session.Session) *EC2Metadata

func (*EC2Metadata) GetInstanceIdentityDocument

func (r *EC2Metadata) GetInstanceIdentityDocument() (ec2metadata.EC2InstanceIdentityDocument, error)

type GinRateLimiter

type GinRateLimiter struct {
	// DeniedHandler is called if the request is disallowed. If it is
	// nil, the DefaultDeniedHandler variable is used.
	DeniedHandler gin.HandlerFunc

	// Error is called if the RateLimiter returns an error. If it is
	// nil, the DefaultErrorFunc is used.
	Error func(*gin.Context, error)

	// Limiter is call for each request to determine whether the
	// request is permitted and update internal state. It must be set.
	RateLimiter throttled.RateLimiter

	// VaryBy is called for each request to generate a key for the
	// limiter. If it is nil, all requests use an empty string key.
	VaryBy func(*gin.Context) string
}

GinRateLimiter faciliates using a Limiter to limit HTTP requests.

func GinContextRateLimit

func GinContextRateLimit(rateLimiter throttled.RateLimiter, contextKey string) *GinRateLimiter

Build context key based rate limiter (e.g. we can store the issuer/tenant as a context param)

func GinIpRateLimit

func GinIpRateLimit(rateLimiter throttled.RateLimiter, contextKey string) *GinRateLimiter

Build IP based rate limiter

func (*GinRateLimiter) RateLimit

func (t *GinRateLimiter) RateLimit() gin.HandlerFunc

Requests that are not limited will be passed to the handler unchanged. Limited requests will be passed to the DeniedHandler. X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After headers will be written to the response based on the values in the RateLimitResult.

type InMemoryGCRARateLimiterFactory

type InMemoryGCRARateLimiterFactory struct{}

func (InMemoryGCRARateLimiterFactory) Build

func (f InMemoryGCRARateLimiterFactory) Build(reqsMinute int, burstSize int) (throttled.RateLimiter, error)

type MultiRateLimiter

type MultiRateLimiter struct {
	// contains filtered or unexported fields
}

Allows different GinRateLimit settings per siteKey (issuer)

implements UpdatableClusterRateLimiter (so it's cluster
aware and it can be updated in execution time)

func (*MultiRateLimiter) Nodes

func (r *MultiRateLimiter) Nodes() int

func (*MultiRateLimiter) RateLimit

func (r *MultiRateLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error)

func (*MultiRateLimiter) UpdateNodeCount

func (r *MultiRateLimiter) UpdateNodeCount(nodes int) error

type NodeCounter

type NodeCounter func() int

func DefaultNodeCounter

func DefaultNodeCounter() NodeCounter

func NewAwsNodeCounter

func NewAwsNodeCounter(EC2 *EC2, autoScaling *AutoScaling,
	iid *ec2metadata.EC2InstanceIdentityDocument, logger logging.Logger) NodeCounter

Node counter for amazon EC2

type RateLimitConfig

type RateLimitConfig struct {
	Enabled bool                         `mapstructure:"enabled"`
	Default RateLimitSettings            `mapstructure:"default"`
	Custom  map[string]RateLimitSettings `mapstructure:"custom"`
}

type RateLimitSettings

type RateLimitSettings struct {
	MaxRequests int `mapstructure:"max_requests"`
	BurstSize   int `mapstructure:"burst_size"`
}

type RateLimiterFactory

type RateLimiterFactory interface {
	Build(reqsMinute int, burstSize int) (throttled.RateLimiter, error)
}

type RateLimiterSettings

type RateLimiterSettings struct {
	// contains filtered or unexported fields
}

type UpdatableClusterRateLimiter

type UpdatableClusterRateLimiter interface {
	throttled.RateLimiter
	UpdateNodeCount(nodes int) error
	Nodes() int
}

func GinRateLimit

func GinRateLimit(cfg RateLimitConfig, nodeCounter NodeCounter, logger logging.Logger) (UpdatableClusterRateLimiter, error)

func NewClusterAwareRateLimiter

func NewClusterAwareRateLimiter(factory RateLimiterFactory, nodes int, settings RateLimiterSettings) (UpdatableClusterRateLimiter, error)

func NewMultiRateLimiter

func NewMultiRateLimiter(factory RateLimiterFactory, nodes int, defaultSettings RateLimiterSettings,
	customSettings map[string]RateLimiterSettings) (UpdatableClusterRateLimiter, error)

type UpdatableRateLimiter

type UpdatableRateLimiter interface {
	Update(settings RateLimiterSettings) error
	RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error)
}

func NewDynamicRateLimiter

func NewDynamicRateLimiter(factory RateLimiterFactory, settings RateLimiterSettings) (UpdatableRateLimiter, error)

type VaryByFunc

type VaryByFunc func(*gin.Context) string

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL