mirror of
https://github.com/dutchcoders/transfer.sh.git
synced 2020-11-18 19:53:40 -08:00
Add ratelimit package
This commit is contained in:
parent
5ccf97b353
commit
7bf499b092
8
vendor/github.com/VojtechVitek/ratelimit/.travis.yml
generated
vendored
Normal file
8
vendor/github.com/VojtechVitek/ratelimit/.travis.yml
generated
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
sudo: false
|
||||
language: go
|
||||
|
||||
go:
|
||||
- 1.7rc2
|
||||
|
||||
script:
|
||||
- go test ./...
|
22
vendor/github.com/VojtechVitek/ratelimit/LICENSE
generated
vendored
Normal file
22
vendor/github.com/VojtechVitek/ratelimit/LICENSE
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2016 Vojtech Vitek
|
||||
|
||||
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.
|
22
vendor/github.com/VojtechVitek/ratelimit/README.md
generated
vendored
Normal file
22
vendor/github.com/VojtechVitek/ratelimit/README.md
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
# Rate Limit HTTP middleware
|
||||
[![GoDoc Widget]][GoDoc] [![Travis Widget]][Travis]
|
||||
|
||||
[Golang](http://golang.org/) package for rate limiting HTTP endpoints based on context and request headers.
|
||||
|
||||
[GoDoc]: https://godoc.org/github.com/VojtechVitek/ratelimit
|
||||
[GoDoc Widget]: https://godoc.org/github.com/VojtechVitek/ratelimit?status.svg
|
||||
[Travis]: https://travis-ci.org/VojtechVitek/ratelimit
|
||||
[Travis Widget]: https://travis-ci.org/VojtechVitek/ratelimit.svg?branch=master
|
||||
|
||||
# Under development
|
||||
|
||||
# Goals
|
||||
- Simple but powerful API
|
||||
- Token Bucket algorithm (rate + burst)
|
||||
- Storage independent (Redis, In-Memory or any other K/V store)
|
||||
|
||||
# License
|
||||
|
||||
Copyright (c) 2016 Vojtech Vitek
|
||||
|
||||
Licensed under the [MIT License](./LICENSE).
|
117
vendor/github.com/VojtechVitek/ratelimit/download.go
generated
vendored
Normal file
117
vendor/github.com/VojtechVitek/ratelimit/download.go
generated
vendored
Normal file
@ -0,0 +1,117 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func DownloadSpeed(keyFn KeyFn) *downloadBuilder {
|
||||
return &downloadBuilder{
|
||||
keyFn: keyFn,
|
||||
}
|
||||
}
|
||||
|
||||
type downloadBuilder struct {
|
||||
keyFn KeyFn
|
||||
rate int
|
||||
window time.Duration
|
||||
}
|
||||
|
||||
func (b *downloadBuilder) Rate(rate int, window time.Duration) *downloadBuilder {
|
||||
b.rate = rate
|
||||
b.window = window
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO: Custom burst?
|
||||
// func (b *downloadBuilder) Burst(burst int) *downloadBuilder {}
|
||||
|
||||
func (b *downloadBuilder) LimitBy(store TokenBucketStore, fallbackStores ...TokenBucketStore) func(http.Handler) http.Handler {
|
||||
store.InitRate(b.rate, b.window)
|
||||
for _, store := range fallbackStores {
|
||||
store.InitRate(b.rate, b.window)
|
||||
}
|
||||
|
||||
downloadLimiter := downloadLimiter{
|
||||
downloadBuilder: b,
|
||||
store: store,
|
||||
fallbackStores: fallbackStores,
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
key := downloadLimiter.keyFn(r)
|
||||
if key == "" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
lw := &limitWriter{
|
||||
ResponseWriter: w,
|
||||
downloadLimiter: &downloadLimiter,
|
||||
key: key,
|
||||
}
|
||||
|
||||
next.ServeHTTP(lw, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
type downloadLimiter struct {
|
||||
*downloadBuilder
|
||||
|
||||
next http.Handler
|
||||
store TokenBucketStore
|
||||
fallbackStores []TokenBucketStore
|
||||
}
|
||||
|
||||
type limitWriter struct {
|
||||
http.ResponseWriter
|
||||
*downloadLimiter
|
||||
|
||||
key string
|
||||
wroteHeader bool
|
||||
canWrite int64
|
||||
}
|
||||
|
||||
func (w *limitWriter) Write(buf []byte) (int, error) {
|
||||
total := 0
|
||||
for {
|
||||
if w.canWrite < 1024 {
|
||||
ok, _, _, err := w.downloadLimiter.store.Take("download:" + w.key)
|
||||
if err != nil {
|
||||
for _, store := range w.fallbackStores {
|
||||
ok, _, _, err = store.Take("download:" + w.key)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
if ok {
|
||||
w.canWrite += 1024
|
||||
}
|
||||
}
|
||||
if w.canWrite == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
max := len(buf) - total
|
||||
if int(w.canWrite) < max {
|
||||
max = int(w.canWrite)
|
||||
}
|
||||
if max == 0 {
|
||||
return total, nil
|
||||
}
|
||||
|
||||
n, err := w.ResponseWriter.Write(buf[total : total+max])
|
||||
w.canWrite -= int64(n)
|
||||
total += n
|
||||
if err != nil {
|
||||
return total, err
|
||||
}
|
||||
}
|
||||
}
|
21
vendor/github.com/VojtechVitek/ratelimit/download_test.go
generated
vendored
Normal file
21
vendor/github.com/VojtechVitek/ratelimit/download_test.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
package ratelimit_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
)
|
||||
|
||||
// Watch the download speed with
|
||||
// wget http://localhost:3333/file -q --show-progress
|
||||
func ExampleDownloadSpeed() {
|
||||
middleware := ratelimit.DownloadSpeed(ratelimit.IP).Rate(1024, time.Second).LimitBy(memory.New())
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "/dev/random")
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", middleware(handler))
|
||||
}
|
46
vendor/github.com/VojtechVitek/ratelimit/examples/download/main.go
generated
vendored
Normal file
46
vendor/github.com/VojtechVitek/ratelimit/examples/download/main.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
"github.com/VojtechVitek/ratelimit/redis"
|
||||
redigo "github.com/garyburd/redigo/redis"
|
||||
"github.com/pressly/chi"
|
||||
"github.com/pressly/chi/middleware"
|
||||
)
|
||||
|
||||
var pool = &redigo.Pool{
|
||||
MaxIdle: 10,
|
||||
MaxActive: 50,
|
||||
IdleTimeout: 300 * time.Second,
|
||||
Wait: false, // Important
|
||||
Dial: func() (redigo.Conn, error) {
|
||||
c, err := redigo.DialTimeout("tcp", "127.0.0.1:6379", 200*time.Millisecond, 100*time.Millisecond, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
TestOnBorrow: func(c redigo.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// wget http://localhost:3333 -q --show-progress
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
|
||||
r.Use(ratelimit.DownloadSpeed(ratelimit.IP).Rate(1024, time.Second).LimitBy(redis.New(pool), memory.New()))
|
||||
r.Get("/", ServeVideo)
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func ServeVideo(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "/Users/vojtechvitek/Desktop/govideo.mov")
|
||||
}
|
55
vendor/github.com/VojtechVitek/ratelimit/examples/request/main.go
generated
vendored
Normal file
55
vendor/github.com/VojtechVitek/ratelimit/examples/request/main.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
"github.com/VojtechVitek/ratelimit/redis"
|
||||
redigo "github.com/garyburd/redigo/redis"
|
||||
"github.com/pressly/chi"
|
||||
"github.com/pressly/chi/middleware"
|
||||
)
|
||||
|
||||
var pool = &redigo.Pool{
|
||||
MaxIdle: 10,
|
||||
MaxActive: 50,
|
||||
IdleTimeout: 300 * time.Second,
|
||||
Wait: false, // Important
|
||||
Dial: func() (redigo.Conn, error) {
|
||||
c, err := redigo.DialTimeout("tcp", "127.0.0.1:6379", 200*time.Millisecond, 100*time.Millisecond, 100*time.Millisecond)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, err
|
||||
},
|
||||
TestOnBorrow: func(c redigo.Conn, t time.Time) error {
|
||||
_, err := c.Do("PING")
|
||||
return err
|
||||
},
|
||||
}
|
||||
|
||||
// while :; do curl -v localhost:3333; sleep 0.1; done
|
||||
func main() {
|
||||
r := chi.NewRouter()
|
||||
r.Use(middleware.Logger)
|
||||
//r.Use(ratelimit.Request(ratelimit.IP).Rate(1, time.Second).LimitBy(redis.New(pool)))
|
||||
|
||||
r.Use(ratelimit.Request(ratelimit.IP).Rate(5, 5*time.Second).LimitBy(redis.New(pool), memory.New()))
|
||||
|
||||
r.Get("/", Hello)
|
||||
|
||||
http.ListenAndServe(":3333", r)
|
||||
}
|
||||
|
||||
func Hello(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello World!\n"))
|
||||
//w.Write([]byte("Hello user_id=" + r.URL.Query().Get("user_id") + "\n"))
|
||||
}
|
||||
|
||||
func UserKey(r *http.Request) string {
|
||||
user := r.URL.Query().Get("user_id")
|
||||
// user, _ := r.Context().Value("session.user_id").(string)
|
||||
return user
|
||||
}
|
24
vendor/github.com/VojtechVitek/ratelimit/examples/throttle/main.go
generated
vendored
Normal file
24
vendor/github.com/VojtechVitek/ratelimit/examples/throttle/main.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
)
|
||||
|
||||
// curl -v http://localhost:3333
|
||||
func main() {
|
||||
middleware := ratelimit.Throttle(1)
|
||||
|
||||
http.ListenAndServe(":3333", middleware(http.HandlerFunc(Work)))
|
||||
}
|
||||
|
||||
func Work(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("working hard...\n\n"))
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
w.Write([]byte("done"))
|
||||
}
|
27
vendor/github.com/VojtechVitek/ratelimit/ip.go
generated
vendored
Normal file
27
vendor/github.com/VojtechVitek/ratelimit/ip.go
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// IP returns unique key per request IP.
|
||||
func IP(r *http.Request) string {
|
||||
ip, _, _ := net.SplitHostPort(r.RemoteAddr)
|
||||
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
|
||||
if i := strings.IndexAny(xff, ",;"); i != -1 {
|
||||
xff = xff[:i]
|
||||
}
|
||||
ip += "," + xff
|
||||
}
|
||||
if xrip := r.Header.Get("X-Real-IP"); xrip != "" {
|
||||
ip += "," + xrip
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
// NOP returns empty key for each request.
|
||||
func NOP(r *http.Request) string {
|
||||
return ""
|
||||
}
|
62
vendor/github.com/VojtechVitek/ratelimit/memory/store.go
generated
vendored
Normal file
62
vendor/github.com/VojtechVitek/ratelimit/memory/store.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type token struct{}
|
||||
|
||||
type bucketStore struct {
|
||||
sync.Mutex // guards buckets
|
||||
buckets map[string]chan token
|
||||
bucketLen int
|
||||
reset time.Time
|
||||
}
|
||||
|
||||
// New creates new in-memory token bucket store.
|
||||
func New() *bucketStore {
|
||||
return &bucketStore{
|
||||
buckets: map[string]chan token{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bucketStore) InitRate(rate int, window time.Duration) {
|
||||
s.bucketLen = rate
|
||||
s.reset = time.Now()
|
||||
|
||||
go func() {
|
||||
interval := time.Duration(int(window) / rate)
|
||||
tick := time.NewTicker(interval)
|
||||
for t := range tick.C {
|
||||
s.Lock()
|
||||
s.reset = t.Add(interval)
|
||||
for key, bucket := range s.buckets {
|
||||
select {
|
||||
case <-bucket:
|
||||
default:
|
||||
delete(s.buckets, key)
|
||||
}
|
||||
}
|
||||
s.Unlock()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Take implements TokenBucketStore interface. It takes token from a bucket
|
||||
// referenced by a given key, if available.
|
||||
func (s *bucketStore) Take(key string) (bool, int, time.Time, error) {
|
||||
s.Lock()
|
||||
bucket, ok := s.buckets[key]
|
||||
if !ok {
|
||||
bucket = make(chan token, s.bucketLen)
|
||||
s.buckets[key] = bucket
|
||||
}
|
||||
s.Unlock()
|
||||
select {
|
||||
case bucket <- token{}:
|
||||
return true, cap(bucket) - len(bucket), s.reset, nil
|
||||
default:
|
||||
return false, 0, s.reset, nil
|
||||
}
|
||||
}
|
16
vendor/github.com/VojtechVitek/ratelimit/ratelimit.go
generated
vendored
Normal file
16
vendor/github.com/VojtechVitek/ratelimit/ratelimit.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TokenBucketStore is an interface for for any storage implementing
|
||||
// Token Bucket algorithm.
|
||||
type TokenBucketStore interface {
|
||||
InitRate(rate int, window time.Duration)
|
||||
Take(key string) (taken bool, remaining int, reset time.Time, err error)
|
||||
}
|
||||
|
||||
// KeyFn is a function returning bucket key depending on request data.
|
||||
type KeyFn func(r *http.Request) string
|
94
vendor/github.com/VojtechVitek/ratelimit/redis/store.go
generated
vendored
Normal file
94
vendor/github.com/VojtechVitek/ratelimit/redis/store.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/garyburd/redigo/redis"
|
||||
)
|
||||
|
||||
var (
|
||||
PrefixKey = "ratelimit:"
|
||||
ErrUnreachable = errors.New("redis is unreachable")
|
||||
RetryAfter = time.Second
|
||||
)
|
||||
|
||||
const skipOnUnhealthy = 1000
|
||||
|
||||
type bucketStore struct {
|
||||
pool *redis.Pool
|
||||
|
||||
rate int
|
||||
windowSeconds int
|
||||
retryAfter *time.Time
|
||||
}
|
||||
|
||||
// New creates new in-memory token bucket store.
|
||||
func New(pool *redis.Pool) *bucketStore {
|
||||
return &bucketStore{
|
||||
pool: pool,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *bucketStore) InitRate(rate int, window time.Duration) {
|
||||
s.rate = rate
|
||||
s.windowSeconds = int(window / time.Second)
|
||||
if s.windowSeconds <= 1 {
|
||||
s.windowSeconds = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Take implements TokenBucketStore interface. It takes token from a bucket
|
||||
// referenced by a given key, if available.
|
||||
func (s *bucketStore) Take(key string) (bool, int, time.Time, error) {
|
||||
if s.retryAfter != nil {
|
||||
if s.retryAfter.After(time.Now()) {
|
||||
return false, 0, time.Time{}, ErrUnreachable
|
||||
}
|
||||
s.retryAfter = nil
|
||||
}
|
||||
c := s.pool.Get()
|
||||
defer c.Close()
|
||||
|
||||
// Number of tokens in the bucket.
|
||||
bucketLen, err := redis.Int(c.Do("LLEN", PrefixKey+key))
|
||||
if err != nil {
|
||||
next := time.Now().Add(time.Second)
|
||||
s.retryAfter = &next
|
||||
return false, 0, time.Time{}, err
|
||||
}
|
||||
|
||||
// Bucket is full.
|
||||
if bucketLen >= s.rate {
|
||||
return false, 0, time.Time{}, nil
|
||||
}
|
||||
|
||||
if bucketLen > 0 {
|
||||
// Bucket most probably exists, try to push a new token into it.
|
||||
// If RPUSHX returns 0 (ie. key expired between LLEN and RPUSHX), we need
|
||||
// to fall-back to RPUSH without returning error.
|
||||
c.Send("MULTI")
|
||||
c.Send("RPUSHX", PrefixKey+key, "")
|
||||
reply, err := redis.Ints(c.Do("EXEC"))
|
||||
if err != nil {
|
||||
next := time.Now().Add(time.Second)
|
||||
s.retryAfter = &next
|
||||
return false, 0, time.Time{}, err
|
||||
}
|
||||
bucketLen = reply[0]
|
||||
if bucketLen > 0 {
|
||||
return true, s.rate - bucketLen - 1, time.Time{}, nil
|
||||
}
|
||||
}
|
||||
|
||||
c.Send("MULTI")
|
||||
c.Send("RPUSH", PrefixKey+key, "")
|
||||
c.Send("EXPIRE", PrefixKey+key, s.windowSeconds)
|
||||
if _, err := c.Do("EXEC"); err != nil {
|
||||
next := time.Now().Add(time.Second)
|
||||
s.retryAfter = &next
|
||||
return false, 0, time.Time{}, err
|
||||
}
|
||||
|
||||
return true, s.rate - bucketLen - 1, time.Time{}, nil
|
||||
}
|
95
vendor/github.com/VojtechVitek/ratelimit/request.go
generated
vendored
Normal file
95
vendor/github.com/VojtechVitek/ratelimit/request.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
package ratelimit
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Request(keyFn KeyFn) *requestBuilder {
|
||||
return &requestBuilder{
|
||||
keyFn: keyFn,
|
||||
}
|
||||
}
|
||||
|
||||
type requestBuilder struct {
|
||||
keyFn KeyFn
|
||||
rate int
|
||||
window time.Duration
|
||||
rateHeader string
|
||||
resetHeader string
|
||||
}
|
||||
|
||||
func (b *requestBuilder) Rate(rate int, window time.Duration) *requestBuilder {
|
||||
b.rate = rate
|
||||
b.window = window
|
||||
b.rateHeader = fmt.Sprintf("%v", float32(rate)*float32(window/time.Second))
|
||||
b.resetHeader = fmt.Sprintf("%d", time.Now().Unix())
|
||||
return b
|
||||
}
|
||||
|
||||
// TODO: Custom burst?
|
||||
// func (b *requestBuilder) Burst(burst int) *requestBuilder {}
|
||||
|
||||
func (b *requestBuilder) LimitBy(store TokenBucketStore, fallbackStores ...TokenBucketStore) func(http.Handler) http.Handler {
|
||||
store.InitRate(b.rate, b.window)
|
||||
for _, store := range fallbackStores {
|
||||
store.InitRate(b.rate, b.window)
|
||||
}
|
||||
|
||||
limiter := requestLimiter{
|
||||
requestBuilder: b,
|
||||
store: store,
|
||||
fallbackStores: fallbackStores,
|
||||
}
|
||||
|
||||
fn := func(next http.Handler) http.Handler {
|
||||
limiter.next = next
|
||||
return &limiter
|
||||
}
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
type requestLimiter struct {
|
||||
*requestBuilder
|
||||
|
||||
next http.Handler
|
||||
store TokenBucketStore
|
||||
fallbackStores []TokenBucketStore
|
||||
}
|
||||
|
||||
// ServeHTTPC implements http.Handler interface.
|
||||
func (l *requestLimiter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
key := l.keyFn(r)
|
||||
if key == "" {
|
||||
l.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
ok, remaining, reset, err := l.store.Take("request:" + key)
|
||||
if err != nil {
|
||||
for _, store := range l.fallbackStores {
|
||||
ok, remaining, reset, err = store.Take("request:" + key)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
l.next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
w.Header().Add("Retry-After", reset.Format(http.TimeFormat))
|
||||
http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
|
||||
return
|
||||
}
|
||||
w.Header().Add("X-RateLimit-Key", key)
|
||||
w.Header().Add("X-RateLimit-Rate", l.rateHeader)
|
||||
w.Header().Add("X-RateLimit-Limit", fmt.Sprintf("%d", l.rate))
|
||||
w.Header().Add("X-RateLimit-Remaining", fmt.Sprintf("%d", remaining))
|
||||
w.Header().Add("X-RateLimit-Reset", fmt.Sprintf("%d", reset.Unix()))
|
||||
w.Header().Add("Retry-After", reset.Format(http.TimeFormat))
|
||||
l.next.ServeHTTP(w, r)
|
||||
}
|
19
vendor/github.com/VojtechVitek/ratelimit/request_test.go
generated
vendored
Normal file
19
vendor/github.com/VojtechVitek/ratelimit/request_test.go
generated
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
package ratelimit_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
"github.com/VojtechVitek/ratelimit/memory"
|
||||
)
|
||||
|
||||
func ExampleRequest() {
|
||||
middleware := ratelimit.Request(ratelimit.IP).Rate(30, time.Minute).LimitBy(memory.New())
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("Hello World!"))
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", middleware(handler))
|
||||
}
|
47
vendor/github.com/VojtechVitek/ratelimit/throttle.go
generated
vendored
Normal file
47
vendor/github.com/VojtechVitek/ratelimit/throttle.go
generated
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
package ratelimit
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Throttle is a middleware that limits number of currently
|
||||
// processed requests at a time.
|
||||
func Throttle(limit int) func(http.Handler) http.Handler {
|
||||
if limit <= 0 {
|
||||
panic("Throttle expects limit > 0")
|
||||
}
|
||||
|
||||
t := throttler{
|
||||
tokens: make(chan token, limit),
|
||||
}
|
||||
for i := 0; i < limit; i++ {
|
||||
t.tokens <- token{}
|
||||
}
|
||||
|
||||
fn := func(h http.Handler) http.Handler {
|
||||
t.h = h
|
||||
return &t
|
||||
}
|
||||
|
||||
return fn
|
||||
}
|
||||
|
||||
// token represents a request that is being processed.
|
||||
type token struct{}
|
||||
|
||||
// throttler limits number of currently processed requests at a time.
|
||||
type throttler struct {
|
||||
h http.Handler
|
||||
tokens chan token
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler interface.
|
||||
func (t *throttler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
select {
|
||||
case <-r.Context().Done():
|
||||
return
|
||||
case tok := <-t.tokens:
|
||||
defer func() {
|
||||
t.tokens <- tok
|
||||
}()
|
||||
t.h.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
23
vendor/github.com/VojtechVitek/ratelimit/throttle_test.go
generated
vendored
Normal file
23
vendor/github.com/VojtechVitek/ratelimit/throttle_test.go
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
package ratelimit_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/VojtechVitek/ratelimit"
|
||||
)
|
||||
|
||||
func ExampleThrottle() {
|
||||
middleware := ratelimit.Throttle(1)
|
||||
|
||||
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("working hard...\n\n"))
|
||||
if f, ok := w.(http.Flusher); ok {
|
||||
f.Flush()
|
||||
}
|
||||
time.Sleep(10 * time.Second)
|
||||
w.Write([]byte("done"))
|
||||
})
|
||||
|
||||
http.ListenAndServe(":3333", middleware(handler))
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user