1
0

feature/rate-limit (#91)

This commit is contained in:
konrad
2019-07-21 21:27:30 +00:00
committed by Gitea
parent 2e599e792e
commit 4327a559e5
33 changed files with 1520 additions and 86 deletions

View File

@ -1,9 +1,9 @@
y.output
# ignore intellij files
.idea
*.iml
*.ipr
*.iws
*.test
y.output
# ignore intellij files
.idea
*.iml
*.ipr
*.iws
*.test

View File

@ -1,18 +1,18 @@
TEST?=./...
default: test
fmt: generate
go fmt ./...
test: generate
go get -t ./...
go test $(TEST) $(TESTARGS)
generate:
go generate ./...
updatedeps:
go get -u golang.org/x/tools/cmd/stringer
.PHONY: default generate test updatedeps
TEST?=./...
default: test
fmt: generate
go fmt ./...
test: generate
go get -t ./...
go test $(TEST) $(TESTARGS)
generate:
go generate ./...
updatedeps:
go get -u golang.org/x/tools/cmd/stringer
.PHONY: default generate test updatedeps

View File

@ -1,29 +1,29 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it

View File

@ -1,21 +1,21 @@
sudo: false
language: go
go:
- 1.9
- "1.10"
- tip
os:
- linux
- osx
matrix:
allow_failures:
- go: tip
fast_finish: true
script:
- go build
- go test -race -v ./...
sudo: false
language: go
go:
- 1.9
- "1.10"
- tip
os:
- linux
- osx
matrix:
allow_failures:
- go: tip
fast_finish: true
script:
- go build
- go test -race -v ./...

25
vendor/github.com/ulule/limiter/v3/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,25 @@
root = true
[*]
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
insert_final_newline = true
charset = utf-8
[*.{yml,yaml}]
indent_size = 2
[*.go]
indent_size = 8
indent_style = tab
[*.json]
indent_size = 4
indent_style = space
[Makefile]
indent_style = tab
indent_size = 4

1
vendor/github.com/ulule/limiter/v3/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
/vendor

81
vendor/github.com/ulule/limiter/v3/.golangci.yml generated vendored Normal file
View File

@ -0,0 +1,81 @@
run:
concurrency: 4
deadline: 1m
issues-exit-code: 1
tests: true
output:
format: colored-line-number
print-issued-lines: true
print-linter-name: true
linters-settings:
errcheck:
check-type-assertions: false
check-blank: false
govet:
check-shadowing: false
use-installed-packages: false
golint:
min-confidence: 0.8
gofmt:
simplify: true
gocyclo:
min-complexity: 10
maligned:
suggest-new: true
dupl:
threshold: 80
goconst:
min-len: 3
min-occurrences: 3
misspell:
locale: US
lll:
line-length: 120
unused:
check-exported: false
unparam:
algo: cha
check-exported: false
nakedret:
max-func-lines: 30
linters:
enable:
- megacheck
- govet
- errcheck
- gas
- structcheck
- varcheck
- ineffassign
- deadcode
- typecheck
- golint
- interfacer
- unconvert
- gocyclo
- gofmt
- misspell
- lll
- nakedret
enable-all: false
disable:
- depguard
- prealloc
- dupl
- maligned
disable-all: false
issues:
exclude-use-default: false
max-per-linter: 1024
max-same: 1024
exclude:
- "G304"
- "G101"
- "G104"

5
vendor/github.com/ulule/limiter/v3/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,5 @@
Primary contributors:
Gilles FABIO <gilles@ulule.com>
Florent MESSA <florent@ulule.com>
Thomas LE ROUX <thomas.leroux@ulule.com>

21
vendor/github.com/ulule/limiter/v3/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-2018 Ulule
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

7
vendor/github.com/ulule/limiter/v3/Makefile generated vendored Normal file
View File

@ -0,0 +1,7 @@
.PHONY: test lint
test:
@(scripts/test)
lint:
@(scripts/lint)

185
vendor/github.com/ulule/limiter/v3/README.md generated vendored Normal file
View File

@ -0,0 +1,185 @@
# Limiter
[![Documentation][godoc-img]][godoc-url]
![License][license-img]
[![Build Status][circle-img]][circle-url]
[![Go Report Card][goreport-img]][goreport-url]
*Dead simple rate limit middleware for Go.*
* Simple API
* "Store" approach for backend
* Redis support (but not tied too)
* Middlewares: HTTP and [Gin][4]
## Installation
Using [Go Modules](https://github.com/golang/go/wiki/Modules)
```bash
$ go get github.com/ulule/limiter/v3@v3.2.0
```
**Dep backport:**
Please use [v3-dep](https://github.com/ulule/limiter/tree/v3-dep) branch.
## Usage
In five steps:
* Create a `limiter.Rate` instance _(the number of requests per period)_
* Create a `limiter.Store` instance _(see [Redis](https://github.com/ulule/limiter/blob/master/drivers/store/redis/store.go) or [In-Memory](https://github.com/ulule/limiter/blob/master/drivers/store/memory/store.go))_
* Create a `limiter.Limiter` instance that takes store and rate instances as arguments
* Create a middleware instance using the middleware of your choice
* Give the limiter instance to your middleware initializer
**Example:**
```go
// Create a rate with the given limit (number of requests) for the given
// period (a time.Duration of your choice).
import "github.com/ulule/limiter/v3"
rate := limiter.Rate{
Period: 1 * time.Hour,
Limit: 1000,
}
// You can also use the simplified format "<limit>-<period>"", with the given
// periods:
//
// * "S": second
// * "M": minute
// * "H": hour
// * "D": day
//
// Examples:
//
// * 5 reqs/second: "5-S"
// * 10 reqs/minute: "10-M"
// * 1000 reqs/hour: "1000-H"
// * 2000 reqs/day: "2000-D"
//
rate, err := limiter.NewRateFromFormatted("1000-H")
if err != nil {
panic(err)
}
// Then, create a store. Here, we use the bundled Redis store. Any store
// compliant to limiter.Store interface will do the job. The defaults are
// "limiter" as Redis key prefix and a maximum of 3 retries for the key under
// race condition.
import "github.com/ulule/limiter/v3/drivers/store/redis"
store, err := redis.NewStore(client)
if err != nil {
panic(err)
}
// Alternatively, you can pass options to the store with the "WithOptions"
// function. For example, for Redis store:
import "github.com/ulule/limiter/v3/drivers/store/redis"
store, err := redis.NewStoreWithOptions(pool, limiter.StoreOptions{
Prefix: "your_own_prefix",
MaxRetry: 4,
})
if err != nil {
panic(err)
}
// Or use a in-memory store with a goroutine which clears expired keys.
import "github.com/ulule/limiter/v3/drivers/store/memory"
store := memory.NewStore()
// Then, create the limiter instance which takes the store and the rate as arguments.
// Now, you can give this instance to any supported middleware.
instance := limiter.New(store, rate)
```
See middleware examples:
* [HTTP](https://github.com/ulule/limiter/tree/master/examples/http/main.go)
* [Gin](https://github.com/ulule/limiter/tree/master/examples/gin/main.go)
* [Beego](https://github.com/ulule/limiter/blob/master/examples/beego/main.go)
* [Chi](https://github.com/ulule/limiter/tree/master/examples/chi/main.go)
* [Echo](https://github.com/ulule/limiter/tree/master/examples/echo/main.go)
## How it works
The ip address of the request is used as a key in the store.
If the key does not exist in the store we set a default
value with an expiration period.
You will find two stores:
* Redis: rely on [TTL](http://redis.io/commands/ttl) and incrementing the rate limit on each request.
* In-Memory: rely on a fork of [go-cache](https://github.com/patrickmn/go-cache) with a goroutine to clear expired keys using a default interval.
When the limit is reached, a `429` HTTP status code is sent.
## Why Yet Another Package
You could ask us: why yet another rate limit package?
Because existing packages did not suit our needs.
We tried a lot of alternatives:
1. [Throttled][1]. This package uses the generic cell-rate algorithm. To cite the
documentation: *"The algorithm has been slightly modified from its usual form to
support limiting with an additional quantity parameter, such as for limiting the
number of bytes uploaded"*. It is brillant in term of algorithm but
documentation is quite unclear at the moment, we don't need *burst* feature for
now, impossible to get a correct `After-Retry` (when limit exceeds, we can still
make a few requests, because of the max burst) and it only supports ``http.Handler``
middleware (we use [Gin][4]). Currently, we only need to return `429`
and `X-Ratelimit-*` headers for `n reqs/duration`.
2. [Speedbump][3]. Good package but maybe too lightweight. No `Reset` support,
only one middleware for [Gin][4] framework and too Redis-coupled. We rather
prefer to use a "store" approach.
3. [Tollbooth][5]. Good one too but does both too much and too little. It limits by
remote IP, path, methods, custom headers and basic auth usernames... but does not
provide any Redis support (only *in-memory*) and a ready-to-go middleware that sets
`X-Ratelimit-*` headers. `tollbooth.LimitByRequest(limiter, r)` only returns an HTTP
code.
4. [ratelimit][2]. Probably the closer to our needs but, once again, too
lightweight, no middleware available and not active (last commit was in August
2014). Some parts of code (Redis) comes from this project. It should deserve much
more love.
There are other many packages on GitHub but most are either too lightweight, too
old (only support old Go versions) or unmaintained. So that's why we decided to
create yet another one.
## Contributing
* Ping us on twitter:
* [@oibafsellig](https://twitter.com/oibafsellig)
* [@thoas](https://twitter.com/thoas)
* [@novln_](https://twitter.com/novln_)
* Fork the [project](https://github.com/ulule/limiter)
* Fix [bugs](https://github.com/ulule/limiter/issues)
Don't hesitate ;)
[1]: https://github.com/throttled/throttled
[2]: https://github.com/r8k/ratelimit
[3]: https://github.com/etcinit/speedbump
[4]: https://github.com/gin-gonic/gin
[5]: https://github.com/didip/tollbooth
[godoc-url]: https://godoc.org/github.com/ulule/limiter
[godoc-img]: https://godoc.org/github.com/ulule/limiter?status.svg
[license-img]: https://img.shields.io/badge/license-MIT-blue.svg
[goreport-url]: https://goreportcard.com/report/github.com/ulule/limiter
[goreport-img]: https://goreportcard.com/badge/github.com/ulule/limiter
[circle-url]: https://circleci.com/gh/ulule/limiter/tree/master
[circle-img]: https://circleci.com/gh/ulule/limiter.svg?style=shield&circle-token=baf62ec320dd871b3a4a7e67fa99530fbc877c99

15
vendor/github.com/ulule/limiter/v3/defaults.go generated vendored Normal file
View File

@ -0,0 +1,15 @@
package limiter
import "time"
const (
// DefaultPrefix is the default prefix to use for the key in the store.
DefaultPrefix = "limiter"
// DefaultMaxRetry is the default maximum number of key retries under
// race condition (mainly used with database-based stores).
DefaultMaxRetry = 3
// DefaultCleanUpInterval is the default time duration for cleanup.
DefaultCleanUpInterval = 30 * time.Second
)

View File

@ -0,0 +1,28 @@
package common
import (
"time"
"github.com/ulule/limiter/v3"
)
// GetContextFromState generate a new limiter.Context from given state.
func GetContextFromState(now time.Time, rate limiter.Rate, expiration time.Time, count int64) limiter.Context {
limit := rate.Limit
remaining := int64(0)
reached := true
if count <= limit {
remaining = limit - count
reached = false
}
reset := expiration.Unix()
return limiter.Context{
Limit: limit,
Remaining: remaining,
Reset: reset,
Reached: reached,
}
}

View File

@ -0,0 +1,159 @@
package memory
import (
"runtime"
"sync"
"time"
)
// Forked from https://github.com/patrickmn/go-cache
// CacheWrapper is used to ensure that the underlying cleaner goroutine used to clean expired keys will not prevent
// Cache from being garbage collected.
type CacheWrapper struct {
*Cache
}
// A cleaner will periodically delete expired keys from cache.
type cleaner struct {
interval time.Duration
stop chan bool
}
// Run will periodically delete expired keys from given cache until GC notify that it should stop.
func (cleaner *cleaner) Run(cache *Cache) {
ticker := time.NewTicker(cleaner.interval)
for {
select {
case <-ticker.C:
cache.Clean()
case <-cleaner.stop:
ticker.Stop()
return
}
}
}
// stopCleaner is a callback from GC used to stop cleaner goroutine.
func stopCleaner(wrapper *CacheWrapper) {
wrapper.cleaner.stop <- true
}
// startCleaner will start a cleaner goroutine for given cache.
func startCleaner(cache *Cache, interval time.Duration) {
cleaner := &cleaner{
interval: interval,
stop: make(chan bool),
}
cache.cleaner = cleaner
go cleaner.Run(cache)
}
// Counter is a simple counter with an optional expiration.
type Counter struct {
Value int64
Expiration int64
}
// Expired returns true if the counter has expired.
func (counter Counter) Expired() bool {
if counter.Expiration == 0 {
return false
}
return time.Now().UnixNano() > counter.Expiration
}
// Cache contains a collection of counters.
type Cache struct {
mutex sync.RWMutex
counters map[string]Counter
cleaner *cleaner
}
// NewCache returns a new cache.
func NewCache(cleanInterval time.Duration) *CacheWrapper {
cache := &Cache{
counters: map[string]Counter{},
}
wrapper := &CacheWrapper{Cache: cache}
if cleanInterval > 0 {
startCleaner(cache, cleanInterval)
runtime.SetFinalizer(wrapper, stopCleaner)
}
return wrapper
}
// Increment increments given value on key.
// If key is undefined or expired, it will create it.
func (cache *Cache) Increment(key string, value int64, duration time.Duration) (int64, time.Time) {
cache.mutex.Lock()
counter, ok := cache.counters[key]
if !ok || counter.Expired() {
expiration := time.Now().Add(duration).UnixNano()
counter = Counter{
Value: value,
Expiration: expiration,
}
cache.counters[key] = counter
cache.mutex.Unlock()
return value, time.Unix(0, expiration)
}
value = counter.Value + value
counter.Value = value
expiration := counter.Expiration
cache.counters[key] = counter
cache.mutex.Unlock()
return value, time.Unix(0, expiration)
}
// Get returns key's value and expiration.
func (cache *Cache) Get(key string, duration time.Duration) (int64, time.Time) {
cache.mutex.RLock()
counter, ok := cache.counters[key]
if !ok || counter.Expired() {
expiration := time.Now().Add(duration).UnixNano()
cache.mutex.RUnlock()
return 0, time.Unix(0, expiration)
}
value := counter.Value
expiration := counter.Expiration
cache.mutex.RUnlock()
return value, time.Unix(0, expiration)
}
// Clean will deleted any expired keys.
func (cache *Cache) Clean() {
now := time.Now().UnixNano()
cache.mutex.Lock()
for key, counter := range cache.counters {
if now > counter.Expiration {
delete(cache.counters, key)
}
}
cache.mutex.Unlock()
}
// Reset changes the key's value and resets the expiration.
func (cache *Cache) Reset(key string, duration time.Duration) (int64, time.Time) {
cache.mutex.Lock()
delete(cache.counters, key)
cache.mutex.Unlock()
expiration := time.Now().Add(duration).UnixNano()
return 0, time.Unix(0, expiration)
}

View File

@ -0,0 +1,67 @@
package memory
import (
"context"
"fmt"
"time"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/common"
)
// Store is the in-memory store.
type Store struct {
// Prefix used for the key.
Prefix string
// cache used to store values in-memory.
cache *CacheWrapper
}
// NewStore creates a new instance of memory store with defaults.
func NewStore() limiter.Store {
return NewStoreWithOptions(limiter.StoreOptions{
Prefix: limiter.DefaultPrefix,
CleanUpInterval: limiter.DefaultCleanUpInterval,
})
}
// NewStoreWithOptions creates a new instance of memory store with options.
func NewStoreWithOptions(options limiter.StoreOptions) limiter.Store {
return &Store{
Prefix: options.Prefix,
cache: NewCache(options.CleanUpInterval),
}
}
// Get returns the limit for given identifier.
func (store *Store) Get(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
count, expiration := store.cache.Increment(key, 1, rate.Period)
lctx := common.GetContextFromState(now, rate, expiration, count)
return lctx, nil
}
// Peek returns the limit for given identifier, without modification on current values.
func (store *Store) Peek(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
count, expiration := store.cache.Get(key, rate.Period)
lctx := common.GetContextFromState(now, rate, expiration, count)
return lctx, nil
}
// Reset returns the limit for given identifier.
func (store *Store) Reset(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
count, expiration := store.cache.Reset(key, rate.Period)
lctx := common.GetContextFromState(now, rate, expiration, count)
return lctx, nil
}

View File

@ -0,0 +1,320 @@
package redis
import (
"context"
"fmt"
"time"
libredis "github.com/go-redis/redis"
"github.com/pkg/errors"
"github.com/ulule/limiter/v3"
"github.com/ulule/limiter/v3/drivers/store/common"
)
// Client is an interface thats allows to use a redis cluster or a redis single client seamlessly.
type Client interface {
Ping() *libredis.StatusCmd
Get(key string) *libredis.StringCmd
Set(key string, value interface{}, expiration time.Duration) *libredis.StatusCmd
Watch(handler func(*libredis.Tx) error, keys ...string) error
Del(keys ...string) *libredis.IntCmd
SetNX(key string, value interface{}, expiration time.Duration) *libredis.BoolCmd
Eval(script string, keys []string, args ...interface{}) *libredis.Cmd
}
// Store is the redis store.
type Store struct {
// Prefix used for the key.
Prefix string
// MaxRetry is the maximum number of retry under race conditions.
MaxRetry int
// client used to communicate with redis server.
client Client
}
// NewStore returns an instance of redis store with defaults.
func NewStore(client Client) (limiter.Store, error) {
return NewStoreWithOptions(client, limiter.StoreOptions{
Prefix: limiter.DefaultPrefix,
CleanUpInterval: limiter.DefaultCleanUpInterval,
MaxRetry: limiter.DefaultMaxRetry,
})
}
// NewStoreWithOptions returns an instance of redis store with options.
func NewStoreWithOptions(client Client, options limiter.StoreOptions) (limiter.Store, error) {
store := &Store{
client: client,
Prefix: options.Prefix,
MaxRetry: options.MaxRetry,
}
if store.MaxRetry <= 0 {
store.MaxRetry = 1
}
_, err := store.ping()
if err != nil {
return nil, err
}
return store, nil
}
// Get returns the limit for given identifier.
func (store *Store) Get(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
lctx := limiter.Context{}
onWatch := func(rtx *libredis.Tx) error {
created, err := store.doSetValue(rtx, key, rate.Period)
if err != nil {
return err
}
if created {
expiration := now.Add(rate.Period)
lctx = common.GetContextFromState(now, rate, expiration, 1)
return nil
}
count, ttl, err := store.doUpdateValue(rtx, key, rate.Period)
if err != nil {
return err
}
expiration := now.Add(rate.Period)
if ttl > 0 {
expiration = now.Add(ttl)
}
lctx = common.GetContextFromState(now, rate, expiration, count)
return nil
}
err := store.client.Watch(onWatch, key)
if err != nil {
err = errors.Wrapf(err, "limiter: cannot get value for %s", key)
return limiter.Context{}, err
}
return lctx, nil
}
// Peek returns the limit for given identifier, without modification on current values.
func (store *Store) Peek(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
lctx := limiter.Context{}
onWatch := func(rtx *libredis.Tx) error {
count, ttl, err := store.doPeekValue(rtx, key)
if err != nil {
return err
}
expiration := now.Add(rate.Period)
if ttl > 0 {
expiration = now.Add(ttl)
}
lctx = common.GetContextFromState(now, rate, expiration, count)
return nil
}
err := store.client.Watch(onWatch, key)
if err != nil {
err = errors.Wrapf(err, "limiter: cannot peek value for %s", key)
return limiter.Context{}, err
}
return lctx, nil
}
// Reset returns the limit for given identifier which is set to zero.
func (store *Store) Reset(ctx context.Context, key string, rate limiter.Rate) (limiter.Context, error) {
key = fmt.Sprintf("%s:%s", store.Prefix, key)
now := time.Now()
lctx := limiter.Context{}
onWatch := func(rtx *libredis.Tx) error {
err := store.doResetValue(rtx, key)
if err != nil {
return err
}
count := int64(0)
expiration := now.Add(rate.Period)
lctx = common.GetContextFromState(now, rate, expiration, count)
return nil
}
err := store.client.Watch(onWatch, key)
if err != nil {
err = errors.Wrapf(err, "limiter: cannot reset value for %s", key)
return limiter.Context{}, err
}
return lctx, nil
}
// doPeekValue will execute peekValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
func (store *Store) doPeekValue(rtx *libredis.Tx, key string) (int64, time.Duration, error) {
for i := 0; i < store.MaxRetry; i++ {
count, ttl, err := peekValue(rtx, key)
if err == nil {
return count, ttl, nil
}
}
return 0, 0, errors.New("retry limit exceeded")
}
// peekValue will retrieve the counter and its expiration for given key.
func peekValue(rtx *libredis.Tx, key string) (int64, time.Duration, error) {
pipe := rtx.Pipeline()
value := pipe.Get(key)
expire := pipe.PTTL(key)
_, err := pipe.Exec()
if err != nil && err != libredis.Nil {
return 0, 0, err
}
count, err := value.Int64()
if err != nil && err != libredis.Nil {
return 0, 0, err
}
ttl, err := expire.Result()
if err != nil {
return 0, 0, err
}
return count, ttl, nil
}
// doSetValue will execute setValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
func (store *Store) doSetValue(rtx *libredis.Tx, key string, expiration time.Duration) (bool, error) {
for i := 0; i < store.MaxRetry; i++ {
created, err := setValue(rtx, key, expiration)
if err == nil {
return created, nil
}
}
return false, errors.New("retry limit exceeded")
}
// setValue will try to initialize a new counter if given key doesn't exists.
func setValue(rtx *libredis.Tx, key string, expiration time.Duration) (bool, error) {
value := rtx.SetNX(key, 1, expiration)
created, err := value.Result()
if err != nil {
return false, err
}
return created, nil
}
// doUpdateValue will execute setValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
func (store *Store) doUpdateValue(rtx *libredis.Tx, key string,
expiration time.Duration) (int64, time.Duration, error) {
for i := 0; i < store.MaxRetry; i++ {
count, ttl, err := updateValue(rtx, key, expiration)
if err == nil {
return count, ttl, nil
}
// If ttl is negative and there is an error, do not retry an update.
if ttl < 0 {
return 0, 0, err
}
}
return 0, 0, errors.New("retry limit exceeded")
}
// updateValue will try to increment the counter identified by given key.
func updateValue(rtx *libredis.Tx, key string, expiration time.Duration) (int64, time.Duration, error) {
pipe := rtx.Pipeline()
value := pipe.Incr(key)
expire := pipe.PTTL(key)
_, err := pipe.Exec()
if err != nil {
return 0, 0, err
}
count, err := value.Result()
if err != nil {
return 0, 0, err
}
ttl, err := expire.Result()
if err != nil {
return 0, 0, err
}
// If ttl is -1ms, we have to define key expiration.
// PTTL return values changed as of Redis 2.8
// Now the command returns -2ms if the key does not exist, and -1ms if the key exists, but there is no expiry set
// We shouldn't try to set an expiry on a key that doesn't exist
if ttl == (-1 * time.Millisecond) {
expire := rtx.Expire(key, expiration)
ok, err := expire.Result()
if err != nil {
return count, ttl, err
}
if !ok {
return count, ttl, errors.New("cannot configure timeout on key")
}
}
return count, ttl, nil
}
// doResetValue will execute resetValue with a retry mecanism (optimistic locking) until store.MaxRetry is reached.
func (store *Store) doResetValue(rtx *libredis.Tx, key string) error {
for i := 0; i < store.MaxRetry; i++ {
err := resetValue(rtx, key)
if err == nil {
return nil
}
}
return errors.New("retry limit exceeded")
}
// resetValue will try to reset the counter identified by given key.
func resetValue(rtx *libredis.Tx, key string) error {
deletion := rtx.Del(key)
count, err := deletion.Result()
if err != nil {
return err
}
if count != 1 {
return errors.New("cannot delete key")
}
return nil
}
// ping checks if redis is alive.
func (store *Store) ping() (bool, error) {
cmd := store.client.Ping()
pong, err := cmd.Result()
if err != nil {
return false, errors.Wrap(err, "limiter: cannot ping redis server")
}
return (pong == "PONG"), nil
}

24
vendor/github.com/ulule/limiter/v3/go.mod generated vendored Normal file
View File

@ -0,0 +1,24 @@
module github.com/ulule/limiter/v3
go 1.12
require (
github.com/astaxie/beego v1.10.0
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 // indirect
github.com/gin-gonic/gin v1.3.0
github.com/go-chi/chi v3.3.3+incompatible
github.com/go-redis/redis v6.14.0+incompatible
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b // indirect
github.com/labstack/echo v3.3.10+incompatible
github.com/labstack/gommon v0.2.9 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
github.com/onsi/gomega v1.4.2 // indirect
github.com/pkg/errors v0.8.0
github.com/stretchr/testify v1.3.0
github.com/ugorji/go v1.1.1 // indirect
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac // indirect
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
gopkg.in/go-playground/validator.v8 v8.18.2 // indirect
)

77
vendor/github.com/ulule/limiter/v3/go.sum generated vendored Normal file
View File

@ -0,0 +1,77 @@
github.com/astaxie/beego v1.10.0 h1:s0OZ1iUO0rl8+lwWZfPK/0GhQi1tFUcIClTevyz48Pg=
github.com/astaxie/beego v1.10.0/go.mod h1:0R4++1tUqERR0WYFWdfkcrsyoVBCG4DgpDGokT3yb+U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7 h1:AzN37oI0cOS+cougNAV9szl6CVoj2RYwzS3DpUQNtlY=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.3.0 h1:kCmZyPklC0gVdL728E6Aj20uYBJV93nj/TkwBTKhFbs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/go-chi/chi v3.3.3+incompatible h1:KHkmBEMNkwKuK4FdQL7N2wOeB9jnIx7jR5wsuSBEFI8=
github.com/go-chi/chi v3.3.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-redis/redis v6.14.0+incompatible h1:AMPZkM7PbsJbilelrJUAyC4xQbGROTOLSuDd7fnMXCI=
github.com/go-redis/redis v6.14.0+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b h1:X61dhFTE1Au92SvyF8HyAwdjWqiSdfBgFR7wTxC0+uU=
github.com/json-iterator/go v0.0.0-20180806060727-1624edc4454b/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-isatty v0.0.8 h1:HLtExJ+uU2HOZ+wI0Tt5DtUDrx8yhUqDcp7fYERX4CE=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w=
github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed h1:uPxWBzB3+mlnjy9W58qY1j/cjyFjutgw/Vhan2zLy/A=
golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

60
vendor/github.com/ulule/limiter/v3/limiter.go generated vendored Normal file
View File

@ -0,0 +1,60 @@
package limiter
import (
"context"
)
// -----------------------------------------------------------------
// Context
// -----------------------------------------------------------------
// Context is the limit context.
type Context struct {
Limit int64
Remaining int64
Reset int64
Reached bool
}
// -----------------------------------------------------------------
// Limiter
// -----------------------------------------------------------------
// Limiter is the limiter instance.
type Limiter struct {
Store Store
Rate Rate
Options Options
}
// New returns an instance of Limiter.
func New(store Store, rate Rate, options ...Option) *Limiter {
opt := Options{
IPv4Mask: DefaultIPv4Mask,
IPv6Mask: DefaultIPv6Mask,
TrustForwardHeader: false,
}
for _, o := range options {
o(&opt)
}
return &Limiter{
Store: store,
Rate: rate,
Options: opt,
}
}
// Get returns the limit for given identifier.
func (limiter *Limiter) Get(ctx context.Context, key string) (Context, error) {
return limiter.Store.Get(ctx, key, limiter.Rate)
}
// Peek returns the limit for given identifier, without modification on current values.
func (limiter *Limiter) Peek(ctx context.Context, key string) (Context, error) {
return limiter.Store.Peek(ctx, key, limiter.Rate)
}
// Reset sets the limit for given identifier to zero.
func (limiter *Limiter) Reset(ctx context.Context, key string) (Context, error) {
return limiter.Store.Reset(ctx, key, limiter.Rate)
}

72
vendor/github.com/ulule/limiter/v3/network.go generated vendored Normal file
View File

@ -0,0 +1,72 @@
package limiter
import (
"net"
"net/http"
"strings"
)
var (
// DefaultIPv4Mask defines the default IPv4 mask used to obtain user IP.
DefaultIPv4Mask = net.CIDRMask(32, 32)
// DefaultIPv6Mask defines the default IPv6 mask used to obtain user IP.
DefaultIPv6Mask = net.CIDRMask(128, 128)
)
// GetIP returns IP address from request.
func (limiter *Limiter) GetIP(r *http.Request) net.IP {
return GetIP(r, limiter.Options)
}
// GetIPWithMask returns IP address from request by applying a mask.
func (limiter *Limiter) GetIPWithMask(r *http.Request) net.IP {
return GetIPWithMask(r, limiter.Options)
}
// GetIPKey extracts IP from request and returns hashed IP to use as store key.
func (limiter *Limiter) GetIPKey(r *http.Request) string {
return limiter.GetIPWithMask(r).String()
}
// GetIP returns IP address from request.
// If options is defined and TrustForwardHeader is true, it will lookup IP in
// X-Forwarded-For and X-Real-IP headers.
func GetIP(r *http.Request, options ...Options) net.IP {
if len(options) >= 1 && options[0].TrustForwardHeader {
ip := r.Header.Get("X-Forwarded-For")
if ip != "" {
parts := strings.SplitN(ip, ",", 2)
part := strings.TrimSpace(parts[0])
return net.ParseIP(part)
}
ip = strings.TrimSpace(r.Header.Get("X-Real-IP"))
if ip != "" {
return net.ParseIP(ip)
}
}
remoteAddr := strings.TrimSpace(r.RemoteAddr)
host, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return net.ParseIP(remoteAddr)
}
return net.ParseIP(host)
}
// GetIPWithMask returns IP address from request by applying a mask.
func GetIPWithMask(r *http.Request, options ...Options) net.IP {
if len(options) == 0 {
return GetIP(r)
}
ip := GetIP(r, options[0])
if ip.To4() != nil {
return ip.Mask(options[0].IPv4Mask)
}
if ip.To16() != nil {
return ip.Mask(options[0].IPv6Mask)
}
return ip
}

39
vendor/github.com/ulule/limiter/v3/options.go generated vendored Normal file
View File

@ -0,0 +1,39 @@
package limiter
import (
"net"
)
// Option is a functional option.
type Option func(*Options)
// Options are limiter options.
type Options struct {
// IPv4Mask defines the mask used to obtain a IPv4 address.
IPv4Mask net.IPMask
// IPv6Mask defines the mask used to obtain a IPv6 address.
IPv6Mask net.IPMask
// TrustForwardHeader enable parsing of X-Real-IP and X-Forwarded-For headers to obtain user IP.
TrustForwardHeader bool
}
// WithIPv4Mask will configure the limiter to use given mask for IPv4 address.
func WithIPv4Mask(mask net.IPMask) Option {
return func(o *Options) {
o.IPv4Mask = mask
}
}
// WithIPv6Mask will configure the limiter to use given mask for IPv6 address.
func WithIPv6Mask(mask net.IPMask) Option {
return func(o *Options) {
o.IPv6Mask = mask
}
}
// WithTrustForwardHeader will configure the limiter to trust X-Real-IP and X-Forwarded-For headers.
func WithTrustForwardHeader(enable bool) Option {
return func(o *Options) {
o.TrustForwardHeader = enable
}
}

54
vendor/github.com/ulule/limiter/v3/rate.go generated vendored Normal file
View File

@ -0,0 +1,54 @@
package limiter
import (
"strconv"
"strings"
"time"
"github.com/pkg/errors"
)
// Rate is the rate.
type Rate struct {
Formatted string
Period time.Duration
Limit int64
}
// NewRateFromFormatted returns the rate from the formatted version.
func NewRateFromFormatted(formatted string) (Rate, error) {
rate := Rate{}
values := strings.Split(formatted, "-")
if len(values) != 2 {
return rate, errors.Errorf("incorrect format '%s'", formatted)
}
periods := map[string]time.Duration{
"S": time.Second, // Second
"M": time.Minute, // Minute
"H": time.Hour, // Hour
"D": time.Hour * 24, // Day
}
limit, period := values[0], strings.ToUpper(values[1])
duration, ok := periods[period]
if !ok {
return rate, errors.Errorf("incorrect period '%s'", period)
}
p := 1 * duration
l, err := strconv.ParseInt(limit, 10, 64)
if err != nil {
return rate, errors.Errorf("incorrect limit '%s'", limit)
}
rate = Rate{
Formatted: formatted,
Period: p,
Limit: l,
}
return rate, nil
}

28
vendor/github.com/ulule/limiter/v3/store.go generated vendored Normal file
View File

@ -0,0 +1,28 @@
package limiter
import (
"context"
"time"
)
// Store is the common interface for limiter stores.
type Store interface {
// Get returns the limit for given identifier.
Get(ctx context.Context, key string, rate Rate) (Context, error)
// Peek returns the limit for given identifier, without modification on current values.
Peek(ctx context.Context, key string, rate Rate) (Context, error)
// Reset resets the limit to zero for given identifier.
Reset(ctx context.Context, key string, rate Rate) (Context, error)
}
// StoreOptions are options for store.
type StoreOptions struct {
// Prefix is the prefix to use for the key.
Prefix string
// MaxRetry is the maximum number of retry under race conditions.
MaxRetry int
// CleanUpInterval is the interval for cleanup.
CleanUpInterval time.Duration
}

5
vendor/modules.txt vendored
View File

@ -168,6 +168,11 @@ github.com/stretchr/testify/assert
github.com/swaggo/swag/cmd/swag
github.com/swaggo/swag
github.com/swaggo/swag/gen
# github.com/ulule/limiter/v3 v3.3.0
github.com/ulule/limiter/v3
github.com/ulule/limiter/v3/drivers/store/memory
github.com/ulule/limiter/v3/drivers/store/redis
github.com/ulule/limiter/v3/drivers/store/common
# github.com/urfave/cli v1.20.0
github.com/urfave/cli
# github.com/valyala/bytebufferpool v1.0.0