diff options
author | Christopher Speller <crspeller@gmail.com> | 2016-05-12 23:56:07 -0400 |
---|---|---|
committer | Christopher Speller <crspeller@gmail.com> | 2016-05-12 23:56:07 -0400 |
commit | 38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8 (patch) | |
tree | a4fde09672192b97d453ad605b030bd5a10c5a45 /vendor/gopkg.in/throttled | |
parent | 84d2482ddbff9564c9ad75b2d30af66e3ddfd44d (diff) | |
download | chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.gz chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.tar.bz2 chat-38ee83e45b4de7edf89bf9f0ef629eb4c6ad0fa8.zip |
Moving to glide
Diffstat (limited to 'vendor/gopkg.in/throttled')
20 files changed, 1140 insertions, 2 deletions
diff --git a/vendor/gopkg.in/throttled/throttled.v1/common_test.go b/vendor/gopkg.in/throttled/throttled.v1/common_test.go new file mode 100644 index 000000000..ddb57fb1c --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/common_test.go @@ -0,0 +1,65 @@ +package throttled + +import ( + "fmt" + "net/http" + "net/http/httptest" + "sync" + "time" + + "github.com/PuerkitoBio/boom/commands" +) + +type stats struct { + sync.Mutex + ok int + dropped int + ts []time.Time + + body func() +} + +func (s *stats) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if s.body != nil { + s.body() + } + s.Lock() + defer s.Unlock() + s.ts = append(s.ts, time.Now()) + s.ok++ + w.WriteHeader(200) +} + +func (s *stats) DeniedHTTP(w http.ResponseWriter, r *http.Request) { + s.Lock() + defer s.Unlock() + s.dropped++ + w.WriteHeader(deniedStatus) +} + +func (s *stats) Stats() (int, int, []time.Time) { + s.Lock() + defer s.Unlock() + return s.ok, s.dropped, s.ts +} + +func runTest(h http.Handler, b ...commands.Boom) []*commands.Report { + srv := httptest.NewServer(h) + defer srv.Close() + + var rpts []*commands.Report + var wg sync.WaitGroup + var mu sync.Mutex + wg.Add(len(b)) + for i, bo := range b { + bo.Req.Url = srv.URL + fmt.Sprintf("/%d", i) + go func(bo commands.Boom) { + mu.Lock() + defer mu.Unlock() + rpts = append(rpts, bo.Run()) + wg.Done() + }(bo) + } + wg.Wait() + return rpts +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go b/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go new file mode 100644 index 000000000..822978e5d --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go @@ -0,0 +1,65 @@ +package throttled + +import ( + "testing" + "time" +) + +func TestDelayer(t *testing.T) { + cases := []struct { + in Delayer + out time.Duration + }{ + 0: {PerSec(1), time.Second}, + 1: {PerSec(2), 500 * time.Millisecond}, + 2: {PerSec(4), 250 * time.Millisecond}, + 3: {PerSec(5), 200 * time.Millisecond}, + 4: {PerSec(10), 100 * time.Millisecond}, + 5: {PerSec(100), 10 * time.Millisecond}, + 6: {PerSec(3), 333333333 * time.Nanosecond}, + 7: {PerMin(1), time.Minute}, + 8: {PerMin(2), 30 * time.Second}, + 9: {PerMin(4), 15 * time.Second}, + 10: {PerMin(5), 12 * time.Second}, + 11: {PerMin(10), 6 * time.Second}, + 12: {PerMin(60), time.Second}, + 13: {PerHour(1), time.Hour}, + 14: {PerHour(2), 30 * time.Minute}, + 15: {PerHour(4), 15 * time.Minute}, + 16: {PerHour(60), time.Minute}, + 17: {PerHour(120), 30 * time.Second}, + 18: {D(time.Second), time.Second}, + 19: {D(5 * time.Minute), 5 * time.Minute}, + 20: {PerSec(200), 5 * time.Millisecond}, + 21: {PerDay(24), time.Hour}, + } + for i, c := range cases { + got := c.in.Delay() + if got != c.out { + t.Errorf("%d: expected %s, got %s", i, c.out, got) + } + } +} + +func TestQuota(t *testing.T) { + cases := []struct { + q Quota + reqs int + win time.Duration + }{ + 0: {PerSec(10), 10, time.Second}, + 1: {PerMin(30), 30, time.Minute}, + 2: {PerHour(124), 124, time.Hour}, + 3: {PerDay(1), 1, 24 * time.Hour}, + 4: {Q{148, 17 * time.Second}, 148, 17 * time.Second}, + } + for i, c := range cases { + r, w := c.q.Quota() + if r != c.reqs { + t.Errorf("%d: expected %d requests, got %d", i, c.reqs, r) + } + if w != c.win { + t.Errorf("%d: expected %s window, got %s", i, c.win, w) + } + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/doc.go b/vendor/gopkg.in/throttled/throttled.v1/doc.go index acf5213b0..a2c8d4c75 100644 --- a/vendor/gopkg.in/throttled/throttled.v1/doc.go +++ b/vendor/gopkg.in/throttled/throttled.v1/doc.go @@ -74,4 +74,4 @@ // The BSD 3-clause license. Copyright (c) 2014 Martin Angers and Contributors. // http://opensource.org/licenses/BSD-3-Clause // -package throttled +package throttled // import "gopkg.in/throttled/throttled.v1" diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/README.md b/vendor/gopkg.in/throttled/throttled.v1/examples/README.md new file mode 100644 index 000000000..6b12dad20 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/README.md @@ -0,0 +1,12 @@ +# Examples + +This directory contains examples for all the throttlers implemented by the throttled package, as well as an example of a custom limiter. + +* custom/ : implements a custom limiter that allows requests to path /a on even seconds, and on path /b on odd seconds. +* interval-many/ : implements a common interval throttler to control two different handlers, one for path /a and another for path /b, so that requests to any one of the handlers go through at the specified interval. +* interval-vary/ : implements an interval throttler that varies by path, so that requests to each different path goes through at the specified interval. +* interval/ : implements an interval throttler so that any request goes through at the specified interval, regardless of path or any other criteria. +* memstats/ : implements a memory-usage throttler that limits access based on current memory statistics. +* rate-limit/ : implements a rate-limiter throttler that varies by path, so that the number of requests allowed are counted based on the requested path. + +Each example app supports a number of command-line flags. Run the example with the -h flag to display usage and defaults. diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/custom/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/custom/main.go new file mode 100644 index 000000000..b3fe993e8 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/custom/main.go @@ -0,0 +1,90 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "gopkg.in/throttled/throttled.v1" +) + +var ( + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +// Custom limiter: allow requests to the /a path on even seconds only, and +// allow access to the /b path on odd seconds only. +// +// Yes this is absurd. A more realistic case could be to allow requests to some +// contest page only during a limited time window. +type customLimiter struct { +} + +func (c *customLimiter) Start() { + // No-op +} + +func (c *customLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) { + s := time.Now().Second() + ch := make(chan bool, 1) + ok := (r.URL.Path == "/a" && s%2 == 0) || (r.URL.Path == "/b" && s%2 != 0) + ch <- ok + if *output == "v" { + log.Printf("Custom Limiter: Path=%s, Second=%d; ok? %v", r.URL.Path, s, ok) + } + return ch, nil +} + +func main() { + flag.Parse() + + var h http.Handler + var ok, ko int + var mu sync.Mutex + + // Keep the start time to print since-time + start := time.Now() + // Create the custom throttler using our custom limiter + t := throttled.Custom(&customLimiter{}) + // Set its denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("KO: %s", time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + // Throttle the OK handler + rand.Seed(time.Now().Unix()) + h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("ok: %s", time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + w.WriteHeader(200) + mu.Lock() + defer mu.Unlock() + ok++ + })) + + // Print stats once in a while + go func() { + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + log.Printf("ok: %d, ko: %d", ok, ko) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", h) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-many/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-many/main.go new file mode 100644 index 000000000..51a4ca023 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-many/main.go @@ -0,0 +1,79 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "gopkg.in/throttled/throttled.v1" +) + +var ( + delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls") + bursts = flag.Int("bursts", 10, "number of bursts allowed") + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +func main() { + flag.Parse() + + var ok, ko int + var mu sync.Mutex + + // Keep start time to log since-time + start := time.Now() + + // Create the interval throttle + t := throttled.Interval(throttled.D(*delay), *bursts, nil, 0) + // Set its denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("%s: KO: %s", r.URL.Path, time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + // Create OK handlers + rand.Seed(time.Now().Unix()) + makeHandler := func(ix int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("handler %d: %s: ok: %s", ix, r.URL.Path, time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + w.WriteHeader(200) + mu.Lock() + defer mu.Unlock() + ok++ + }) + } + // Throttle them using the same interval throttler + h1 := t.Throttle(makeHandler(1)) + h2 := t.Throttle(makeHandler(2)) + + // Handle two paths + mux := http.NewServeMux() + mux.Handle("/a", h1) + mux.Handle("/b", h2) + + // Print stats once in a while + go func() { + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + log.Printf("ok: %d, ko: %d", ok, ko) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", mux) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/main.go new file mode 100644 index 000000000..f43cdc122 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/main.go @@ -0,0 +1,74 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "gopkg.in/throttled/throttled.v1" +) + +var ( + delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls") + bursts = flag.Int("bursts", 10, "number of bursts allowed") + maxkeys = flag.Int("max-keys", 1000, "maximum number of keys") + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +func main() { + flag.Parse() + + var h http.Handler + var ok, ko int + var mu sync.Mutex + + // Keep the start time to print since-time + start := time.Now() + + // Create the interval throttler + t := throttled.Interval(throttled.D(*delay), *bursts, &throttled.VaryBy{ + Path: true, + }, *maxkeys) + // Set the denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("KO: %s", time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + + // Throttle the OK handler + rand.Seed(time.Now().Unix()) + h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("%s: ok: %s", r.URL.Path, time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + w.WriteHeader(200) + mu.Lock() + defer mu.Unlock() + ok++ + })) + + // Print stats once in a while + go func() { + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + log.Printf("ok: %d, ko: %d", ok, ko) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", h) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/siege-urls b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/siege-urls new file mode 100644 index 000000000..9a2d0d312 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/siege-urls @@ -0,0 +1,4 @@ +http://localhost:9000/a +http://localhost:9000/b +http://localhost:9000/c + diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/interval/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/interval/main.go new file mode 100644 index 000000000..ef8ee2cb8 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/interval/main.go @@ -0,0 +1,69 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "gopkg.in/throttled/throttled.v1" +) + +var ( + delay = flag.Duration("delay", 200*time.Millisecond, "delay between calls") + bursts = flag.Int("bursts", 10, "number of bursts allowed") + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +func main() { + flag.Parse() + + var h http.Handler + var ok, ko int + var mu sync.Mutex + + // Keep the start time to print since-time + start := time.Now() + // Create the interval throttler + t := throttled.Interval(throttled.D(*delay), *bursts, nil, 0) + // Set its denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("KO: %s", time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + // Throttle the OK handler + rand.Seed(time.Now().Unix()) + h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("ok: %s", time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + w.WriteHeader(200) + mu.Lock() + defer mu.Unlock() + ok++ + })) + + // Print stats once in a while + go func() { + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + log.Printf("ok: %d, ko: %d", ok, ko) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", h) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/main.go new file mode 100644 index 000000000..50d4cc69b --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/main.go @@ -0,0 +1,97 @@ +package main + +import ( + "flag" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net/http" + "runtime" + "sync" + "time" + + "gopkg.in/throttled/throttled.v1" +) + +var ( + numgc = flag.Int("gc", 0, "number of GC runs") + mallocs = flag.Int("mallocs", 0, "number of mallocs") + total = flag.Int("total", 0, "total number of bytes allocated") + allocs = flag.Int("allocs", 0, "number of bytes allocated") + refrate = flag.Duration("refresh", 0, "refresh rate of the memory stats") + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +func main() { + flag.Parse() + + var h http.Handler + var ok, ko int + var mu sync.Mutex + + // Keep the start time to print since-time + start := time.Now() + // Create the thresholds struct + thresh := throttled.MemThresholds(&runtime.MemStats{ + NumGC: uint32(*numgc), + Mallocs: uint64(*mallocs), + TotalAlloc: uint64(*total), + Alloc: uint64(*allocs), + }) + if *output != "q" { + log.Printf("thresholds: NumGC: %d, Mallocs: %d, Alloc: %dKb, Total: %dKb", thresh.NumGC, thresh.Mallocs, thresh.Alloc/1024, thresh.TotalAlloc/1024) + } + // Create the MemStats throttler + t := throttled.MemStats(thresh, *refrate) + // Set its denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("KO: %s", time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + + // Throttle the OK handler + rand.Seed(time.Now().Unix()) + h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("ok: %s", time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + // Read the whole file in memory, to actually use 64Kb (instead of streaming to w) + b, err := ioutil.ReadFile("test-file") + if err != nil { + throttled.Error(w, r, err) + return + } + _, err = w.Write(b) + if err != nil { + throttled.Error(w, r, err) + } + mu.Lock() + defer mu.Unlock() + ok++ + })) + + // Print stats once in a while + go func() { + var mem runtime.MemStats + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + runtime.ReadMemStats(&mem) + log.Printf("ok: %d, ko: %d", ok, ko) + log.Printf("TotalAllocs: %d Kb, Allocs: %d Kb, Mallocs: %d, NumGC: %d", mem.TotalAlloc/1024, mem.Alloc/1024, mem.Mallocs, mem.NumGC) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", h) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file b/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file Binary files differnew file mode 100644 index 000000000..c97c12f9b --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file diff --git a/vendor/gopkg.in/throttled/throttled.v1/examples/rate-limit/main.go b/vendor/gopkg.in/throttled/throttled.v1/examples/rate-limit/main.go new file mode 100644 index 000000000..b00119f63 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/examples/rate-limit/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "flag" + "fmt" + "log" + "math/rand" + "net/http" + "sync" + "time" + + "github.com/garyburd/redigo/redis" + "gopkg.in/throttled/throttled.v1" + "gopkg.in/throttled/throttled.v1/store" +) + +var ( + requests = flag.Int("requests", 10, "number of requests allowed in the time window") + window = flag.Duration("window", time.Minute, "time window for the limit of requests") + storeType = flag.String("store", "mem", "store to use, one of `mem` or `redis` (on default localhost port)") + delayRes = flag.Duration("delay-response", 0, "delay the response by a random duration between 0 and this value") + output = flag.String("output", "v", "type of output, one of `v`erbose, `q`uiet, `ok`-only, `ko`-only") +) + +func main() { + flag.Parse() + + var h http.Handler + var ok, ko int + var mu sync.Mutex + var st throttled.Store + + // Keep the start time to print since-time + start := time.Now() + // Create the rate-limit store + switch *storeType { + case "mem": + st = store.NewMemStore(0) + case "redis": + st = store.NewRedisStore(setupRedis(), "throttled:", 0) + default: + log.Fatalf("unsupported store: %s", *storeType) + } + // Create the rate-limit throttler, varying on path + t := throttled.RateLimit(throttled.Q{Requests: *requests, Window: *window}, &throttled.VaryBy{ + Path: true, + }, st) + + // Set its denied handler + t.DeniedHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ko" { + log.Printf("KO: %s", time.Since(start)) + } + throttled.DefaultDeniedHandler.ServeHTTP(w, r) + mu.Lock() + defer mu.Unlock() + ko++ + }) + + // Throttle the OK handler + rand.Seed(time.Now().Unix()) + h = t.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if *output == "v" || *output == "ok" { + log.Printf("ok: %s", time.Since(start)) + } + if *delayRes > 0 { + wait := time.Duration(rand.Intn(int(*delayRes))) + time.Sleep(wait) + } + w.WriteHeader(200) + mu.Lock() + defer mu.Unlock() + ok++ + })) + + // Print stats once in a while + go func() { + for _ = range time.Tick(10 * time.Second) { + mu.Lock() + log.Printf("ok: %d, ko: %d", ok, ko) + mu.Unlock() + } + }() + fmt.Println("server listening on port 9000") + http.ListenAndServe(":9000", h) +} + +func setupRedis() *redis.Pool { + pool := &redis.Pool{ + MaxIdle: 3, + IdleTimeout: 30 * time.Second, + Dial: func() (redis.Conn, error) { + return redis.Dial("tcp", ":6379") + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + return pool +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/interval_test.go b/vendor/gopkg.in/throttled/throttled.v1/interval_test.go new file mode 100644 index 000000000..bc584e134 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/interval_test.go @@ -0,0 +1,114 @@ +package throttled + +import ( + "net/http" + "testing" + + "github.com/PuerkitoBio/boom/commands" +) + +func TestInterval(t *testing.T) { + if testing.Short() { + t.Skip() + } + cases := []struct { + n int + c int + rps int + bursts int + }{ + 0: {60, 10, 20, 100}, + 1: {300, 20, 100, 100}, + 2: {10, 10, 1, 10}, + 3: {1000, 100, 1000, 100}, + } + for i, c := range cases { + // Setup the stats handler + st := &stats{} + // Create the throttler + th := Interval(PerSec(c.rps), c.bursts, nil, 0) + th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP) + b := commands.Boom{ + Req: &commands.ReqOpts{}, + N: c.n, + C: c.c, + Output: "quiet", + } + // Run the test + rpts := runTest(th.Throttle(st), b) + // Assert results + for _, rpt := range rpts { + assertRPS(t, i, c.rps, rpt) + } + assertStats(t, i, st, rpts) + } +} + +func TestIntervalVary(t *testing.T) { + if testing.Short() { + t.Skip() + } + cases := []struct { + n int + c int + urls int + rps int + bursts int + }{ + 0: {60, 10, 3, 20, 100}, + 1: {300, 20, 3, 100, 100}, + 2: {10, 10, 3, 1, 10}, + 3: {500, 10, 2, 1000, 100}, + } + for i, c := range cases { + // Setup the stats handler + st := &stats{} + // Create the throttler + th := Interval(PerSec(c.rps), c.bursts, nil, 0) + th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP) + var booms []commands.Boom + for j := 0; j < c.urls; j++ { + booms = append(booms, commands.Boom{ + Req: &commands.ReqOpts{}, + N: c.n, + C: c.c, + Output: "quiet", + }) + } + // Run the test + rpts := runTest(th.Throttle(st), booms...) + // Assert results + for _, rpt := range rpts { + assertRPS(t, i, c.rps, rpt) + } + assertStats(t, i, st, rpts) + } +} + +func assertRPS(t *testing.T, ix int, exp int, rpt *commands.Report) { + wigglef := 0.2 * float64(exp) + if rpt.SuccessRPS < float64(exp)-wigglef || rpt.SuccessRPS > float64(exp)+wigglef { + t.Errorf("%d: expected RPS to be around %d, got %f", ix, exp, rpt.SuccessRPS) + } +} + +func assertStats(t *testing.T, ix int, st *stats, rpts []*commands.Report) { + ok, ko, _ := st.Stats() + var twos, fives, max int + for _, rpt := range rpts { + twos += rpt.StatusCodeDist[200] + fives += rpt.StatusCodeDist[deniedStatus] + if len(rpt.StatusCodeDist) > max { + max = len(rpt.StatusCodeDist) + } + } + if ok != twos { + t.Errorf("%d: expected %d status 200, got %d", ix, twos, ok) + } + if ko != fives { + t.Errorf("%d: expected %d status 429, got %d", ix, fives, ok) + } + if max > 2 { + t.Errorf("%d: expected at most 2 different status codes, got %d", ix, max) + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go b/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go new file mode 100644 index 000000000..2b8faa721 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go @@ -0,0 +1,64 @@ +package throttled + +import ( + "net/http" + "runtime" + "testing" + "time" + + "github.com/PuerkitoBio/boom/commands" +) + +func TestMemStats(t *testing.T) { + if testing.Short() { + t.Skip() + } + cases := []struct { + n int + c int + gc uint32 + total uint64 + rate time.Duration + }{ + 0: {1000, 10, 3, 0, 0}, + 1: {200, 10, 0, 600000, 0}, + 2: {500, 10, 2, 555555, 10 * time.Millisecond}, + } + for i, c := range cases { + // Setup the stats handler + st := &stats{} + // Create the throttler + limit := MemThresholds(&runtime.MemStats{NumGC: c.gc, TotalAlloc: c.total}) + th := MemStats(limit, c.rate) + th.DeniedHandler = http.HandlerFunc(st.DeniedHTTP) + // Run the test + b := commands.Boom{ + Req: &commands.ReqOpts{}, + N: c.n, + C: c.c, + Output: "quiet", + } + rpts := runTest(th.Throttle(st), b) + // Assert results + assertStats(t, i, st, rpts) + assertMem(t, i, limit) + } +} + +func assertMem(t *testing.T, ix int, limit *runtime.MemStats) { + var mem runtime.MemStats + runtime.ReadMemStats(&mem) + if mem.NumGC < limit.NumGC { + t.Errorf("%d: expected gc to be at least %d, got %d", ix, limit.NumGC, mem.NumGC) + } + if mem.TotalAlloc < limit.TotalAlloc { + t.Errorf("%d: expected total alloc to be at least %dKb, got %dKb", ix, limit.TotalAlloc/1024, mem.TotalAlloc/1024) + } +} + +func BenchmarkReadMemStats(b *testing.B) { + var mem runtime.MemStats + for i := 0; i < b.N; i++ { + runtime.ReadMemStats(&mem) + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/misc/pre-commit b/vendor/gopkg.in/throttled/throttled.v1/misc/pre-commit new file mode 100755 index 000000000..88b61bfde --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/misc/pre-commit @@ -0,0 +1,38 @@ +#!/bin/sh +# Copyright 2012 The Go Authors. All rights reserved. +# Use of this source code is governed by a BSD-style +# license that can be found in the LICENSE file. + +# git gofmt pre-commit hook +# +# To use, store as .git/hooks/pre-commit inside your repository and make sure +# it has execute permissions. +# +# This script does not handle file names that contain spaces. + +# golint is purely informational, it doesn't fail with exit code != 0 if it finds something, +# because it may find a lot of false positives. Just print out its result for information. +echo "lint result (informational only):" +echo +golint . + +# go vet returns 1 if an error was found. Exit the hook with this exit code. +go vet ./... +vetres=$? + +# Check for gofmt problems and report if any. +gofiles=$(git diff --cached --name-only --diff-filter=ACM | grep '.go$') +[ -z "$gofiles" ] && echo "EXIT $vetres" && exit $vetres + +unformatted=$(gofmt -l $gofiles) +[ -z "$unformatted" ] && echo "EXIT $vetres" && exit $vetres + +# Some files are not gofmt'd. Print message and fail. + +echo >&2 "Go files must be formatted with gofmt. Please run:" +for fn in $unformatted; do + echo >&2 " gofmt -w $PWD/$fn" +done + +echo "EXIT 1" +exit 1 diff --git a/vendor/gopkg.in/throttled/throttled.v1/rate_test.go b/vendor/gopkg.in/throttled/throttled.v1/rate_test.go new file mode 100644 index 000000000..67dea74b1 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/rate_test.go @@ -0,0 +1,101 @@ +package throttled + +import ( + "net/http" + "net/http/httptest" + "strconv" + "testing" + "time" +) + +const deniedStatus = 429 + +// Simple memory store for tests, unsafe for concurrent access +type mapStore struct { + cnt map[string]int + ts map[string]time.Time +} + +func newMapStore() *mapStore { + return &mapStore{ + make(map[string]int), + make(map[string]time.Time), + } +} +func (ms *mapStore) Incr(key string, window time.Duration) (int, int, error) { + if _, ok := ms.cnt[key]; !ok { + return 0, 0, ErrNoSuchKey + } + ms.cnt[key]++ + ts := ms.ts[key] + return ms.cnt[key], RemainingSeconds(ts, window), nil +} +func (ms *mapStore) Reset(key string, win time.Duration) error { + ms.cnt[key] = 1 + ms.ts[key] = time.Now().UTC() + return nil +} + +func TestRateLimit(t *testing.T) { + quota := Q{5, 5 * time.Second} + cases := []struct { + limit, remain, reset, status int + }{ + 0: {5, 4, 5, 200}, + 1: {5, 3, 4, 200}, + 2: {5, 2, 4, 200}, + 3: {5, 1, 3, 200}, + 4: {5, 0, 3, 200}, + 5: {5, 0, 2, deniedStatus}, + } + // Limit the requests to 2 per second + th := Interval(PerSec(2), 0, nil, 0) + // Rate limit + rl := RateLimit(quota, nil, newMapStore()) + // Create the stats + st := &stats{} + // Create the handler + h := th.Throttle(rl.Throttle(st)) + + // Start the server + srv := httptest.NewServer(h) + defer srv.Close() + for i, c := range cases { + callRateLimited(t, i, c.limit, c.remain, c.reset, c.status, srv.URL) + } + // Wait 3 seconds and call again, should start a new window + time.Sleep(3 * time.Second) + callRateLimited(t, len(cases), 5, 4, 5, 200, srv.URL) +} + +func callRateLimited(t *testing.T, i, limit, remain, reset, status int, url string) { + res, err := http.Get(url) + if err != nil { + t.Fatal(err) + } + defer res.Body.Close() + // Assert status code + if status != res.StatusCode { + t.Errorf("%d: expected status %d, got %d", i, status, res.StatusCode) + } + // Assert headers + if v := res.Header.Get("X-RateLimit-Limit"); v != strconv.Itoa(limit) { + t.Errorf("%d: expected limit header to be %d, got %s", i, limit, v) + } + if v := res.Header.Get("X-RateLimit-Remaining"); v != strconv.Itoa(remain) { + t.Errorf("%d: expected remain header to be %d, got %s", i, remain, v) + } + // Allow 1 second wiggle room + v := res.Header.Get("X-RateLimit-Reset") + vi, _ := strconv.Atoi(v) + if vi < reset-1 || vi > reset+1 { + t.Errorf("%d: expected reset header to be close to %d, got %d", i, reset, vi) + } + if status == deniedStatus { + v := res.Header.Get("Retry-After") + vi, _ := strconv.Atoi(v) + if vi < reset-1 || vi > reset+1 { + t.Errorf("%d: expected retry after header to be close to %d, got %d", i, reset, vi) + } + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/doc.go b/vendor/gopkg.in/throttled/throttled.v1/store/doc.go index adb4618d3..8e33f2c98 100644 --- a/vendor/gopkg.in/throttled/throttled.v1/store/doc.go +++ b/vendor/gopkg.in/throttled/throttled.v1/store/doc.go @@ -1,2 +1,2 @@ // Package store offers a memory-based and a Redis-based throttled.Store implementation. -package store +package store // import "gopkg.in/throttled/throttled.v1/store" diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go b/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go new file mode 100644 index 000000000..e8ef8d0da --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go @@ -0,0 +1,43 @@ +package store + +import ( + "testing" + "time" +) + +func TestMemStore(t *testing.T) { + st := NewMemStore(0) + win := time.Second + + // Reset stores a key with count of 1, current timestamp + err := st.Reset("k", time.Second) + if err != nil { + t.Errorf("expected reset to return nil, got %s", err) + } + cnt, sec1, _ := st.Incr("k", win) + if cnt != 2 { + t.Errorf("expected reset+incr to set count to 2, got %d", cnt) + } + + // Incr increments the key, keeps same timestamp + cnt, sec2, err := st.Incr("k", win) + if err != nil { + t.Errorf("expected 2nd incr to return nil error, got %s", err) + } + if cnt != 3 { + t.Errorf("expected 2nd incr to return 3, got %d", cnt) + } + if sec1 != sec2 { + t.Errorf("expected 2nd incr to return %d secs, got %d", sec1, sec2) + } + + // Reset on existing key brings it back to 1, new timestamp + err = st.Reset("k", win) + if err != nil { + t.Errorf("expected reset on existing key to return nil, got %s", err) + } + cnt, _, _ = st.Incr("k", win) + if cnt != 2 { + t.Errorf("expected last reset+incr to return 2, got %d", cnt) + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go b/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go new file mode 100644 index 000000000..a282d6d25 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go @@ -0,0 +1,66 @@ +package store + +import ( + "testing" + "time" + + "github.com/garyburd/redigo/redis" +) + +func getPool() *redis.Pool { + pool := &redis.Pool{ + MaxIdle: 3, + IdleTimeout: 30 * time.Second, + Dial: func() (redis.Conn, error) { + return redis.Dial("tcp", ":6379") + }, + TestOnBorrow: func(c redis.Conn, t time.Time) error { + _, err := c.Do("PING") + return err + }, + } + return pool +} + +func TestRedisStore(t *testing.T) { + pool := getPool() + c := pool.Get() + if _, err := redis.String(c.Do("PING")); err != nil { + c.Close() + t.Skip("redis server not available on localhost port 6379") + } + st := NewRedisStore(pool, "throttled:", 1) + win := 2 * time.Second + + // Incr increments the key, even if it does not exist + cnt, secs, err := st.Incr("k", win) + if err != nil { + t.Errorf("expected initial incr to return nil error, got %s", err) + } + if cnt != 1 { + t.Errorf("expected initial incr to return 1, got %d", cnt) + } + if secs != int(win.Seconds()) { + t.Errorf("expected initial incr to return %d secs, got %d", int(win.Seconds()), secs) + } + + // Waiting a second diminishes the remaining seconds + time.Sleep(time.Second) + _, sec2, _ := st.Incr("k", win) + if sec2 != secs-1 { + t.Errorf("expected 2nd incr after a 1s sleep to return %d secs, got %d", secs-1, sec2) + } + + // Waiting a second so the key expires, Incr should set back to 1, initial secs + time.Sleep(1100 * time.Millisecond) + cnt, sec3, err := st.Incr("k", win) + if err != nil { + t.Errorf("expected last incr to return nil error, got %s", err) + } + if cnt != 1 { + t.Errorf("expected last incr to return 1, got %d", cnt) + } + if sec3 != int(win.Seconds()) { + t.Errorf("expected last incr to return %d secs, got %d", int(win.Seconds()), sec3) + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go b/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go new file mode 100644 index 000000000..91b7ae0ae --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go @@ -0,0 +1,56 @@ +package throttled + +import ( + "net/http" + "net/url" + "testing" +) + +func TestVaryBy(t *testing.T) { + u, err := url.Parse("http://localhost/test/path?q=s") + if err != nil { + panic(err) + } + ck := &http.Cookie{Name: "ssn", Value: "test"} + cases := []struct { + vb *VaryBy + r *http.Request + k string + }{ + 0: {nil, &http.Request{}, ""}, + 1: {&VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"}, + 2: { + &VaryBy{Method: true, Path: true}, + &http.Request{Method: "POST", URL: u}, + "post\n/test/path\n", + }, + 3: { + &VaryBy{Headers: []string{"Content-length"}}, + &http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}}, + "123\n", + }, + 4: { + &VaryBy{Separator: ",", Method: true, Headers: []string{"Content-length"}, Params: []string{"q", "user"}}, + &http.Request{Method: "GET", Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}, Form: url.Values{"q": []string{"s"}, "pwd": []string{"secret"}, "user": []string{"test"}}}, + "get,123,s,test,", + }, + 5: { + &VaryBy{Cookies: []string{"ssn"}}, + &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, + "test\n", + }, + 6: { + &VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string { + return "blah" + }}, + &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, + "blah", + }, + } + for i, c := range cases { + got := c.vb.Key(c.r) + if got != c.k { + t.Errorf("%d: expected '%s' (%d), got '%s' (%d)", i, c.k, len(c.k), got, len(got)) + } + } +} |