diff options
author | Christopher Speller <crspeller@gmail.com> | 2016-09-23 10:17:51 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-23 10:17:51 -0400 |
commit | 2ca0e8f9a0f9863555a26e984cde15efff9ef8f8 (patch) | |
tree | daae1ee67b14a3d0a84424f2a304885d9e75ce2b /vendor/gopkg.in/throttled | |
parent | 6d62d65b2dc85855aabea036cbd44f6059e19d13 (diff) | |
download | chat-2ca0e8f9a0f9863555a26e984cde15efff9ef8f8.tar.gz chat-2ca0e8f9a0f9863555a26e984cde15efff9ef8f8.tar.bz2 chat-2ca0e8f9a0f9863555a26e984cde15efff9ef8f8.zip |
Updating golang dependancies (#4075)
Diffstat (limited to 'vendor/gopkg.in/throttled')
53 files changed, 1622 insertions, 2167 deletions
diff --git a/vendor/gopkg.in/throttled/throttled.v1/.gitignore b/vendor/gopkg.in/throttled/throttled.v1/.gitignore deleted file mode 100644 index c2a6499b4..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -.DS_Store -*.swp -*.swo -*.test -examples/interval/interval -examples/interval-vary/interval-vary -examples/interval-many/interval-many -examples/memstats/memstats -examples/rate-limit/rate-limit -examples/custom/custom diff --git a/vendor/gopkg.in/throttled/throttled.v1/.travis.yml b/vendor/gopkg.in/throttled/throttled.v1/.travis.yml deleted file mode 100644 index 1b2427202..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/.travis.yml +++ /dev/null @@ -1,10 +0,0 @@ -sudo: false -language: go - -go: - - 1.2 - - tip - -install: go get -t ./... - -script: go test -v -short ./... diff --git a/vendor/gopkg.in/throttled/throttled.v1/README.md b/vendor/gopkg.in/throttled/throttled.v1/README.md deleted file mode 100644 index fbb4a01fc..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Throttled [![build status](https://secure.travis-ci.org/throttled/throttled.png)](http://travis-ci.org/throttled/throttled) [![GoDoc](https://godoc.org/gopkg.in/throttled/throttled.v1?status.png)](http://godoc.org/gopkg.in/throttled/throttled.v1) - -Package throttled implements different throttling strategies for controlling -access to HTTP handlers. - -*As of July 27, 2015, the package is now located under its own GitHub - organization and uses gopkg.in for versioning, please adjust your - imports to `gopkg.in/throttled/throttled.v1`.* - -## Installation - -`go get gopkg.in/throttled/throttled.v1/...` - -## Interval - -The Interval function creates a throttler that allows requests to go through at -a controlled, constant interval. The interval may be applied to all requests -(vary argument == nil) or independently based on vary-by criteria. - -For example: - - th := throttled.Interval(throttled.PerSec(10), 100, &throttled.VaryBy{Path: true}, 50) - h := th.Throttle(myHandler) - http.ListenAndServe(":9000", h) - -Creates a throttler that will allow a request each 100ms (10 requests per second), with -a buffer of 100 exceeding requests before dropping requests with a status code 429 (by -default, configurable using th.DeniedHandler or the package-global DefaultDeniedHandler -variable). Different paths will be throttled independently, so that /path_a and /path_b -both can serve 10 requests per second. The last argument, 50, indicates the maximum number -of keys that the throttler will keep in memory. - -## MemStats - -The MemStats function creates a throttler that allows requests to go through only if -the memory statistics of the current process are below specified thresholds. - -For example: - - th := throttled.MemStats(throttled.MemThresholds(&runtime.MemStats{NumGC: 10}, 10*time.Millisecond) - h := th.Throttle(myHandler) - http.ListenAndServe(":9000", h) - -Creates a throttler that will allow requests to go through until the number of garbage -collections reaches the initial number + 10 (the MemThresholds function creates absolute -memory stats thresholds from offsets). The second argument, 10ms, indicates the refresh -rate of the memory stats. - -## RateLimit - -The RateLimit function creates a throttler that allows a certain number of requests in -a given time window, as is often implemented in public RESTful APIs. - -For example: - - th := throttled.RateLimit(throttled.PerMin(30), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000)) - h := th.Throttle(myHandler) - http.ListenAndServe(":9000", h) - -Creates a throttler that will limit requests to 30 per minute, based on the remote address -of the client, and will store the counter and remaining time of the current window in the -provided memory store, limiting the number of keys to keep in memory to 1000. The store -sub-package also provides a Redis-based Store implementations. - -The RateLimit throttler sets the expected X-RateLimit-* headers on the response, and -also sets a Retry-After header when the limit is exceeded. - -## Documentation - -The API documentation is available as usual on [godoc.org][doc]. - -There is also a [blog post explaining the package's usage on 0value.com][blog]. - -Finally, many examples are provided in the /examples sub-folder of the repository. - -## License - -The [BSD 3-clause license][bsd]. Copyright (c) 2014 Martin Angers and Contributors. - -[doc]: http://godoc.org/gopkg.in/throttled/throttled.v1 -[blog]: http://0value.com/throttled--guardian-of-the-web-server -[bsd]: http://opensource.org/licenses/BSD-3-Clause diff --git a/vendor/gopkg.in/throttled/throttled.v1/common_test.go b/vendor/gopkg.in/throttled/throttled.v1/common_test.go deleted file mode 100644 index ddb57fb1c..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/common_test.go +++ /dev/null @@ -1,65 +0,0 @@ -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.go b/vendor/gopkg.in/throttled/throttled.v1/delayer.go deleted file mode 100644 index e62ec9e86..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/delayer.go +++ /dev/null @@ -1,109 +0,0 @@ -package throttled - -import "time" - -// The Quota interface defines the method to implement to describe -// a time-window quota, as required by the RateLimit throttler. -type Quota interface { - // Quota returns a number of requests allowed, and a duration. - Quota() (int, time.Duration) -} - -// The Delayer interface defines the method to implement to describe -// a delay as required by the Interval throttler. -type Delayer interface { - // Delay returns a duration. - Delay() time.Duration -} - -// PerSec represents a number of requests per second. -type PerSec int - -// Delay returns the duration to wait before the next request can go through, -// so that PerSec(n) == n requests per second at regular intervals. -func (ps PerSec) Delay() time.Duration { - if ps <= 0 { - return 0 - } - return time.Duration(1.0 / float64(ps) * float64(time.Second)) -} - -// Quota returns the number of requests allowed in a 1 second time window, -// so that PerSec(n) == n requests allowed per second. -func (ps PerSec) Quota() (int, time.Duration) { - return int(ps), time.Second -} - -// PerMin represents a number of requests per minute. -type PerMin int - -// Delay returns the duration to wait before the next request can go through, -// so that PerMin(n) == n requests per minute at regular intervals. -func (pm PerMin) Delay() time.Duration { - if pm <= 0 { - return 0 - } - return time.Duration(1.0 / float64(pm) * float64(time.Minute)) -} - -// Quota returns the number of requests allowed in a 1 minute time window, -// so that PerMin(n) == n requests allowed per minute. -func (pm PerMin) Quota() (int, time.Duration) { - return int(pm), time.Minute -} - -// PerHour represents a number of requests per hour. -type PerHour int - -// Delay returns the duration to wait before the next request can go through, -// so that PerHour(n) == n requests per hour at regular intervals. -func (ph PerHour) Delay() time.Duration { - if ph <= 0 { - return 0 - } - return time.Duration(1.0 / float64(ph) * float64(time.Hour)) -} - -// Quota returns the number of requests allowed in a 1 hour time window, -// so that PerHour(n) == n requests allowed per hour. -func (ph PerHour) Quota() (int, time.Duration) { - return int(ph), time.Hour -} - -// PerDay represents a number of requests per day. -type PerDay int - -// Delay returns the duration to wait before the next request can go through, -// so that PerDay(n) == n requests per day at regular intervals. -func (pd PerDay) Delay() time.Duration { - if pd <= 0 { - return 0 - } - return time.Duration(1.0 / float64(pd) * float64(24*time.Hour)) -} - -// Quota returns the number of requests allowed in a 1 day time window, -// so that PerDay(n) == n requests allowed per day. -func (pd PerDay) Quota() (int, time.Duration) { - return int(pd), 24 * time.Hour -} - -// D represents a custom delay. -type D time.Duration - -// Delay returns the duration to wait before the next request can go through, -// which is the custom duration represented by the D value. -func (d D) Delay() time.Duration { - return time.Duration(d) -} - -// Q represents a custom quota. -type Q struct { - Requests int - Window time.Duration -} - -// Quota returns the number of requests allowed and the custom time window. -func (q Q) Quota() (int, time.Duration) { - return q.Requests, q.Window -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go b/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go deleted file mode 100644 index 822978e5d..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/delayer_test.go +++ /dev/null @@ -1,65 +0,0 @@ -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 deleted file mode 100644 index a2c8d4c75..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/doc.go +++ /dev/null @@ -1,77 +0,0 @@ -// Package throttled implements different throttling strategies for controlling -// access to HTTP handlers. -// -// Installation -// -// go get gopkg.in/throttled/throttled.v1/... -// -// Inverval -// -// The Interval function creates a throttler that allows requests to go through at -// a controlled, constant interval. The interval may be applied to all requests -// (vary argument == nil) or independently based on vary-by criteria. -// -// For example: -// -// th := throttled.Interval(throttled.PerSec(10), 100, &throttled.VaryBy{Path: true}, 50) -// h := th.Throttle(myHandler) -// http.ListenAndServe(":9000", h) -// -// Creates a throttler that will allow a request each 100ms (10 requests per second), with -// a buffer of 100 exceeding requests before dropping requests with a status code 429 (by -// default, configurable using th.DeniedHandler or the package-global DefaultDeniedHandler -// variable). Different paths will be throttled independently, so that /path_a and /path_b -// both can serve 10 requests per second. The last argument, 50, indicates the maximum number -// of keys that the throttler will keep in memory. -// -// MemStats -// -// The MemStats function creates a throttler that allows requests to go through only if -// the memory statistics of the current process are below specified thresholds. -// -// For example: -// -// th := throttled.MemStats(throttled.MemThresholds(&runtime.MemStats{NumGC: 10}, 10*time.Millisecond) -// h := th.Throttle(myHandler) -// http.ListenAndServe(":9000", h) -// -// Creates a throttler that will allow requests to go through until the number of garbage -// collections reaches the initial number + 10 (the MemThresholds function creates absolute -// memory stats thresholds from offsets). The second argument, 10ms, indicates the refresh -// rate of the memory stats. -// -// RateLimit -// -// The RateLimit function creates a throttler that allows a certain number of requests in -// a given time window, as is often implemented in public RESTful APIs. -// -// For example: -// -// th := throttled.RateLimit(throttled.PerMin(30), &throttled.VaryBy{RemoteAddr: true}, store.NewMemStore(1000)) -// h := th.Throttle(myHandler) -// http.ListenAndServe(":9000", h) -// -// Creates a throttler that will limit requests to 30 per minute, based on the remote address -// of the client, and will store the counter and remaining time of the current window in the -// provided memory store, limiting the number of keys to keep in memory to 1000. The store -// sub-package also provides a Redis-based Store implementations. -// -// The RateLimit throttler sets the expected X-RateLimit-* headers on the response, and -// also sets a Retry-After header when the limit is exceeded. -// -// Documentation -// -// The API documentation is available as usual on godoc.org: -// http://godoc.org/gopkg.in/throttled/throttled.v1 -// -// There is also a blog post explaining the package's usage on 0value.com: -// http://0value.com/throttled--guardian-of-the-web-server -// -// Finally, many examples are provided in the /examples sub-folder of the repository. -// -// License -// -// The BSD 3-clause license. Copyright (c) 2014 Martin Angers and Contributors. -// http://opensource.org/licenses/BSD-3-Clause -// -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 deleted file mode 100644 index 6b12dad20..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# 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 deleted file mode 100644 index b3fe993e8..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/custom/main.go +++ /dev/null @@ -1,90 +0,0 @@ -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 deleted file mode 100644 index 51a4ca023..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-many/main.go +++ /dev/null @@ -1,79 +0,0 @@ -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 deleted file mode 100644 index f43cdc122..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/main.go +++ /dev/null @@ -1,74 +0,0 @@ -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 deleted file mode 100644 index 9a2d0d312..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/siege-urls +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index ef8ee2cb8..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/interval/main.go +++ /dev/null @@ -1,69 +0,0 @@ -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 deleted file mode 100644 index 50d4cc69b..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/main.go +++ /dev/null @@ -1,97 +0,0 @@ -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 differdeleted file mode 100644 index c97c12f9b..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file +++ /dev/null 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 deleted file mode 100644 index b00119f63..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/examples/rate-limit/main.go +++ /dev/null @@ -1,101 +0,0 @@ -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.go b/vendor/gopkg.in/throttled/throttled.v1/interval.go deleted file mode 100644 index 628a5593e..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/interval.go +++ /dev/null @@ -1,164 +0,0 @@ -package throttled - -import ( - "net/http" - "sync" - "time" - - "github.com/golang/groupcache/lru" -) - -// Static check to ensure that the interval limiters implement the Limiter interface. -var _ Limiter = (*intervalVaryByLimiter)(nil) -var _ Limiter = (*intervalLimiter)(nil) - -// Interval creates a throttler that controls the requests so that they -// go through at a constant interval. The interval is specified by the -// delay argument, and convenience types such as PerSec can be used to -// express the interval in a more expressive way, i.e. PerSec(10) means -// 10 requests per second or one request each 100ms, PerMin(30) means -// 30 requests per minute or on request each 2s, etc. -// -// The bursts argument indicates the number of exceeding requests that may -// be queued up waiting to be processed. Requests that overflow the queue -// are dropped and go through the denied handler, which may be specified -// on the Throttler and that defaults to the package-global variable -// DefaultDeniedHandler. -// -// The vary argument indicates the criteria to use to group the requests, -// so that the interval applies to the requests in the same group (e.g. based on -// the path, or the remote IP address, etc.). If this argument is nil, the -// interval applies to all requests going through this throttler. -// -// The maxKeys indicates the maximum number of keys to keep in memory to apply the interval, -// when a vary argument is specified. A LRU algorithm is used to remove older keys. -// -func Interval(delay Delayer, bursts int, vary *VaryBy, maxKeys int) *Throttler { - var l Limiter - if vary != nil { - if maxKeys < 1 { - maxKeys = 1 - } - l = &intervalVaryByLimiter{ - delay: delay.Delay(), - bursts: bursts, - vary: vary, - maxKeys: maxKeys, - } - } else { - l = &intervalLimiter{ - delay: delay.Delay(), - bursts: bursts, - } - } - return &Throttler{ - limiter: l, - } -} - -// The intervalLimiter struct implements an interval limiter with no vary-by -// criteria. -type intervalLimiter struct { - delay time.Duration - bursts int - - bucket chan chan bool -} - -// Start initializes the limiter for execution. -func (il *intervalLimiter) Start() { - if il.bursts < 0 { - il.bursts = 0 - } - il.bucket = make(chan chan bool, il.bursts) - go process(il.bucket, il.delay) -} - -// Limit is called for each request to the throttled handler. It tries to -// queue the request to allow it to run at the given interval, but if the -// queue is full, the request is denied access. -func (il *intervalLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) { - ch := make(chan bool, 1) - select { - case il.bucket <- ch: - return ch, nil - default: - ch <- false - return ch, nil - } -} - -// The intervalVaryByLimiter struct implements an interval limiter with a vary-by -// criteria. -type intervalVaryByLimiter struct { - delay time.Duration - bursts int - vary *VaryBy - - lock sync.RWMutex - keys *lru.Cache - maxKeys int -} - -// Start initializes the limiter for execution. -func (il *intervalVaryByLimiter) Start() { - if il.bursts < 0 { - il.bursts = 0 - } - il.keys = lru.New(il.maxKeys) - il.keys.OnEvicted = il.stopProcess -} - -// Limit is called for each request to the throttled handler. It tries to -// queue the request for the vary-by key to allow it to run at the given interval, -// but if the queue is full, the request is denied access. -func (il *intervalVaryByLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) { - ch := make(chan bool, 1) - key := il.vary.Key(r) - - il.lock.RLock() - item, ok := il.keys.Get(key) - if !ok { - // Create the key, bucket, start goroutine - // First release the read lock and acquire a write lock - il.lock.RUnlock() - il.lock.Lock() - // Create the bucket, add the key - bucket := make(chan chan bool, il.bursts) - il.keys.Add(key, bucket) - // Start the goroutine to process this bucket - go process(bucket, il.delay) - item = bucket - // Release the write lock, acquire the read lock - il.lock.Unlock() - il.lock.RLock() - } - defer il.lock.RUnlock() - bucket := item.(chan chan bool) - select { - case bucket <- ch: - return ch, nil - default: - ch <- false - return ch, nil - } -} - -// process loops through the queued requests for a key's bucket, and sends -// requests through at the given interval. -func process(bucket chan chan bool, delay time.Duration) { - after := time.After(0) - for v := range bucket { - <-after - // Let the request go through - v <- true - // Wait the required duration - after = time.After(delay) - } -} - -// stopProcess is called when a key is removed from the LRU cache so that its -// accompanying goroutine is correctly released. -func (il *intervalVaryByLimiter) stopProcess(key lru.Key, value interface{}) { - close(value.(chan chan bool)) -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/interval_test.go b/vendor/gopkg.in/throttled/throttled.v1/interval_test.go deleted file mode 100644 index bc584e134..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/interval_test.go +++ /dev/null @@ -1,114 +0,0 @@ -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.go b/vendor/gopkg.in/throttled/throttled.v1/memstats.go deleted file mode 100644 index bd2765630..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/memstats.go +++ /dev/null @@ -1,214 +0,0 @@ -package throttled - -import ( - "net/http" - "runtime" - "sync" - "time" -) - -// Static check to ensure that memStatsLimiter implements Limiter. -var _ Limiter = (*memStatsLimiter)(nil) - -// The memStatsLimiter struct implements a limiter based on the memory statistics -// of the current process. -type memStatsLimiter struct { - thresholds *runtime.MemStats - refreshRate time.Duration - - lockStats sync.RWMutex - stats runtime.MemStats -} - -// MemStats creates a Throttler based on the memory statistics of the current process. -// Any combination of any (non-array) integer field of Go's runtime.MemStats structure -// can be used as thresholds to deny a request. -// -// As soon as one threshold value is reached, the access is denied. If the value can -// decrease, access will be allowed again once it gets back under the threshold value. -// Denied requests go through the denied handler, which may be specified on the Throttler -// and that defaults to the package-global variable DefaultDeniedHandler. -// -// Thresholds must be specified in absolute numbers (i.e. NumGC = 10 means stop once the -// NumGC reaches 10, not when the current value increments by 10), and zero values are -// ignored. -// -// The refreshRate indicates the frequency at which the process' memory stats are refreshed, -// and 0 means on each request. -// -func MemStats(thresholds *runtime.MemStats, refreshRate time.Duration) *Throttler { - return &Throttler{ - limiter: &memStatsLimiter{ - thresholds: thresholds, - refreshRate: refreshRate, - }, - } -} - -// Start initialized the limiter for execution. -func (m *memStatsLimiter) Start() { - // Make sure there is an initial MemStats reading - runtime.ReadMemStats(&m.stats) - if m.refreshRate > 0 { - go m.refresh() - } -} - -// refresh runs in a separate goroutine and refreshes the memory statistics -// at regular intervals. -func (m *memStatsLimiter) refresh() { - c := time.Tick(m.refreshRate) - for _ = range c { - m.lockStats.Lock() - runtime.ReadMemStats(&m.stats) - m.lockStats.Unlock() - } -} - -// Limit is called for each request to the throttled handler. It checks if -// the request can go through by checking the memory thresholds, and signals it -// via the returned channel. -func (m *memStatsLimiter) Limit(w http.ResponseWriter, r *http.Request) (<-chan bool, error) { - ch := make(chan bool, 1) - // Check if memory thresholds are reached - ch <- m.allow() - return ch, nil -} - -// allow compares the current memory stats with the thresholds, and returns -// false if any threshold is reached. -func (m *memStatsLimiter) allow() bool { - m.lockStats.RLock() - mem := m.stats - m.lockStats.RUnlock() - // If refreshRate == 0, then read on every request. - if m.refreshRate == 0 { - runtime.ReadMemStats(&mem) - } - ok := true - checkStat(m.thresholds.Alloc, mem.Alloc, &ok) - checkStat(m.thresholds.BuckHashSys, mem.BuckHashSys, &ok) - checkStat(m.thresholds.Frees, mem.Frees, &ok) - checkStat(m.thresholds.GCSys, mem.GCSys, &ok) - checkStat(m.thresholds.HeapAlloc, mem.HeapAlloc, &ok) - checkStat(m.thresholds.HeapIdle, mem.HeapIdle, &ok) - checkStat(m.thresholds.HeapInuse, mem.HeapInuse, &ok) - checkStat(m.thresholds.HeapObjects, mem.HeapObjects, &ok) - checkStat(m.thresholds.HeapReleased, mem.HeapReleased, &ok) - checkStat(m.thresholds.HeapSys, mem.HeapSys, &ok) - checkStat(m.thresholds.LastGC, mem.LastGC, &ok) - checkStat(m.thresholds.Lookups, mem.Lookups, &ok) - checkStat(m.thresholds.MCacheInuse, mem.MCacheInuse, &ok) - checkStat(m.thresholds.MCacheSys, mem.MCacheSys, &ok) - checkStat(m.thresholds.MSpanInuse, mem.MSpanInuse, &ok) - checkStat(m.thresholds.MSpanSys, mem.MSpanSys, &ok) - checkStat(m.thresholds.Mallocs, mem.Mallocs, &ok) - checkStat(m.thresholds.NextGC, mem.NextGC, &ok) - checkStat(uint64(m.thresholds.NumGC), uint64(mem.NumGC), &ok) - checkStat(m.thresholds.OtherSys, mem.OtherSys, &ok) - checkStat(m.thresholds.PauseTotalNs, mem.PauseTotalNs, &ok) - checkStat(m.thresholds.StackInuse, mem.StackInuse, &ok) - checkStat(m.thresholds.StackSys, mem.StackSys, &ok) - checkStat(m.thresholds.Sys, mem.Sys, &ok) - checkStat(m.thresholds.TotalAlloc, mem.TotalAlloc, &ok) - return ok -} - -// Checks the threshold value against the actual value, and assigns false -// to the boolean pointer if the threshold is reached. -func checkStat(threshold, actual uint64, ok *bool) { - if !*ok { - return - } - if threshold > 0 { - if actual >= threshold { - *ok = false - } - } -} - -// MemThresholds is a convenience function to create a thresholds memory stats from -// offsets to apply to the current memory stats. Zero values in the offset stats -// are left to 0 in the resulting thresholds memory stats value. -// -// The return value may be used as thresholds argument to the MemStats function. -func MemThresholds(offset *runtime.MemStats) *runtime.MemStats { - var mem, thr runtime.MemStats - runtime.ReadMemStats(&mem) - if offset.Alloc > 0 { - thr.Alloc = mem.Alloc + offset.Alloc - } - if offset.BuckHashSys > 0 { - thr.BuckHashSys = mem.BuckHashSys + offset.BuckHashSys - } - if offset.Frees > 0 { - thr.Frees = mem.Frees + offset.Frees - } - if offset.GCSys > 0 { - thr.GCSys = mem.GCSys + offset.GCSys - } - if offset.HeapAlloc > 0 { - thr.HeapAlloc = mem.HeapAlloc + offset.HeapAlloc - } - if offset.HeapIdle > 0 { - thr.HeapIdle = mem.HeapIdle + offset.HeapIdle - } - if offset.HeapInuse > 0 { - thr.HeapInuse = mem.HeapInuse + offset.HeapInuse - } - if offset.HeapObjects > 0 { - thr.HeapObjects = mem.HeapObjects + offset.HeapObjects - } - if offset.HeapReleased > 0 { - thr.HeapReleased = mem.HeapReleased + offset.HeapReleased - } - if offset.HeapSys > 0 { - thr.HeapSys = mem.HeapSys + offset.HeapSys - } - if offset.LastGC > 0 { - thr.LastGC = mem.LastGC + offset.LastGC - } - if offset.Lookups > 0 { - thr.Lookups = mem.Lookups + offset.Lookups - } - if offset.MCacheInuse > 0 { - thr.MCacheInuse = mem.MCacheInuse + offset.MCacheInuse - } - if offset.MCacheSys > 0 { - thr.MCacheSys = mem.MCacheSys + offset.MCacheSys - } - if offset.MSpanInuse > 0 { - thr.MSpanInuse = mem.MSpanInuse + offset.MSpanInuse - } - if offset.MSpanSys > 0 { - thr.MSpanSys = mem.MSpanSys + offset.MSpanSys - } - if offset.Mallocs > 0 { - thr.Mallocs = mem.Mallocs + offset.Mallocs - } - if offset.NextGC > 0 { - thr.NextGC = mem.NextGC + offset.NextGC - } - if offset.NumGC > 0 { - thr.NumGC = mem.NumGC + offset.NumGC - } - if offset.OtherSys > 0 { - thr.OtherSys = mem.OtherSys + offset.OtherSys - } - if offset.PauseTotalNs > 0 { - thr.PauseTotalNs = mem.PauseTotalNs + offset.PauseTotalNs - } - if offset.StackInuse > 0 { - thr.StackInuse = mem.StackInuse + offset.StackInuse - } - if offset.StackSys > 0 { - thr.StackSys = mem.StackSys + offset.StackSys - } - if offset.Sys > 0 { - thr.Sys = mem.Sys + offset.Sys - } - if offset.TotalAlloc > 0 { - thr.TotalAlloc = mem.TotalAlloc + offset.TotalAlloc - } - return &thr -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go b/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go deleted file mode 100644 index 2b8faa721..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/memstats_test.go +++ /dev/null @@ -1,64 +0,0 @@ -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 deleted file mode 100755 index 88b61bfde..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/misc/pre-commit +++ /dev/null @@ -1,38 +0,0 @@ -#!/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.go b/vendor/gopkg.in/throttled/throttled.v1/rate.go deleted file mode 100644 index d7a7de6d7..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/rate.go +++ /dev/null @@ -1,116 +0,0 @@ -package throttled - -import ( - "math" - "net/http" - "strconv" - "time" -) - -// Static check to ensure that rateLimiter implements Limiter. -var _ Limiter = (*rateLimiter)(nil) - -// RateLimit creates a throttler that limits the number of requests allowed -// in a certain time window defined by the Quota q. The q parameter specifies -// the requests per time window, and it is silently set to at least 1 request -// and at least a 1 second window if it is less than that. The time window -// starts when the first request is made outside an existing window. Fractions -// of seconds are not supported, they are truncated. -// -// The vary parameter indicates what criteria should be used to group requests -// for which the limit must be applied (ex.: rate limit based on the remote address). -// See varyby.go for the various options. -// -// The specified store is used to keep track of the request count and the -// time remaining in the window. The throttled package comes with some stores -// in the throttled/store package. Custom stores can be created too, by implementing -// the Store interface. -// -// Requests that bust the rate limit are denied access and go through the denied handler, -// which may be specified on the Throttler and that defaults to the package-global -// variable DefaultDeniedHandler. -// -// The rate limit throttler sets the following headers on the response: -// -// X-RateLimit-Limit : quota -// X-RateLimit-Remaining : number of requests remaining in the current window -// X-RateLimit-Reset : seconds before a new window -// -// Additionally, if the request was denied access, the following header is added: -// -// Retry-After : seconds before the caller should retry -// -func RateLimit(q Quota, vary *VaryBy, store Store) *Throttler { - // Extract requests and window - reqs, win := q.Quota() - - // Create and return the throttler - return &Throttler{ - limiter: &rateLimiter{ - reqs: reqs, - window: win, - vary: vary, - store: store, - }, - } -} - -// The rate limiter implements limiting the request to a certain quota -// based on the vary-by criteria. State is saved in the store. -type rateLimiter struct { - reqs int - window time.Duration - vary *VaryBy - store Store -} - -// Start initializes the limiter for execution. -func (r *rateLimiter) Start() { - if r.reqs < 1 { - r.reqs = 1 - } - if r.window < time.Second { - r.window = time.Second - } -} - -// Limit is called for each request to the throttled handler. It checks if -// the request can go through and signals it via the returned channel. -// It returns an error if the operation fails. -func (r *rateLimiter) Limit(w http.ResponseWriter, req *http.Request) (<-chan bool, error) { - // Create return channel and initialize - ch := make(chan bool, 1) - ok := true - key := r.vary.Key(req) - - // Get the current count and remaining seconds - cnt, secs, err := r.store.Incr(key, r.window) - // Handle the possible situations: error, begin new window, or increment current window. - switch { - case err != nil && err != ErrNoSuchKey: - // An unexpected error occurred - return nil, err - case err == ErrNoSuchKey || secs <= 0: - // Reset counter - if err := r.store.Reset(key, r.window); err != nil { - return nil, err - } - cnt = 1 - secs = int(r.window.Seconds()) - default: - // If the limit is reached, deny access - if cnt > r.reqs { - ok = false - } - } - // Set rate-limit headers - w.Header().Add("X-RateLimit-Limit", strconv.Itoa(r.reqs)) - w.Header().Add("X-RateLimit-Remaining", strconv.Itoa(int(math.Max(float64(r.reqs-cnt), 0)))) - w.Header().Add("X-RateLimit-Reset", strconv.Itoa(secs)) - if !ok { - w.Header().Add("Retry-After", strconv.Itoa(secs)) - } - // Send response via the return channel - ch <- ok - return ch, nil -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/rate_test.go b/vendor/gopkg.in/throttled/throttled.v1/rate_test.go deleted file mode 100644 index 67dea74b1..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/rate_test.go +++ /dev/null @@ -1,101 +0,0 @@ -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.go b/vendor/gopkg.in/throttled/throttled.v1/store.go deleted file mode 100644 index 760fe2b69..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store.go +++ /dev/null @@ -1,31 +0,0 @@ -package throttled - -import ( - "errors" - "time" -) - -// The error returned if the key does not exist in the Store. -var ErrNoSuchKey = errors.New("throttled: no such key") - -// Store is the interface to implement to store the RateLimit state (number -// of requests per key, time-to-live or creation timestamp). -type Store interface { - // Incr increments the count for the specified key and returns the new value along - // with the number of seconds remaining. It may return an error - // if the operation fails. - // - // The method may return ErrNoSuchKey if the key to increment does not exist, - // in which case Reset will be called to initialize the value. - Incr(string, time.Duration) (int, int, error) - - // Reset resets the key to 1 with the specified window duration. It must create the - // key if it doesn't exist. It returns an error if it fails. - Reset(string, time.Duration) error -} - -// RemainingSeconds is a helper function that returns the number of seconds -// remaining from an absolute timestamp in UTC. -func RemainingSeconds(ts time.Time, window time.Duration) int { - return int((window - time.Now().UTC().Sub(ts)).Seconds()) -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/doc.go b/vendor/gopkg.in/throttled/throttled.v1/store/doc.go deleted file mode 100644 index 8e33f2c98..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package store offers a memory-based and a Redis-based throttled.Store implementation. -package store // import "gopkg.in/throttled/throttled.v1/store" diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/mem.go b/vendor/gopkg.in/throttled/throttled.v1/store/mem.go deleted file mode 100644 index 22d200e8d..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store/mem.go +++ /dev/null @@ -1,90 +0,0 @@ -package store - -import ( - "sync" - "time" - - "github.com/golang/groupcache/lru" - "gopkg.in/throttled/throttled.v1" -) - -// memStore implements an in-memory Store. -type memStore struct { - sync.Mutex - keys *lru.Cache - m map[string]*counter -} - -// NewMemStore creates a new MemStore. If maxKeys > 0, the number of different keys -// is restricted to the specified amount. In this case, it uses an LRU algorithm to -// evict older keys to make room for newer ones. If a request is made for a key that -// has been evicted, it will be processed as if its count was 0, possibly allowing requests -// that should be denied. -// -// If maxKeys <= 0, there is no limit on the number of keys, which may use an unbounded amount of -// memory depending on the server's load. -// -// The MemStore is only for single-process rate-limiting. To share the rate limit state -// among multiple instances of the web server, use a database- or key-value-based -// store. -// -func NewMemStore(maxKeys int) throttled.Store { - var m *memStore - if maxKeys > 0 { - m = &memStore{ - keys: lru.New(maxKeys), - } - } else { - m = &memStore{ - m: make(map[string]*counter), - } - } - return m -} - -// A counter represents a single entry in the MemStore. -type counter struct { - n int - ts time.Time -} - -// Incr increments the counter for the specified key. It returns the new -// count value and the remaining number of seconds, or an error. -func (ms *memStore) Incr(key string, window time.Duration) (int, int, error) { - ms.Lock() - defer ms.Unlock() - var c *counter - if ms.keys != nil { - v, _ := ms.keys.Get(key) - if v != nil { - c = v.(*counter) - } - } else { - c = ms.m[key] - } - if c == nil { - c = &counter{0, time.Now().UTC()} - } - c.n++ - if ms.keys != nil { - ms.keys.Add(key, c) - } else { - ms.m[key] = c - } - return c.n, throttled.RemainingSeconds(c.ts, window), nil -} - -// Reset resets the counter for the specified key. It sets the count -// to 1 and initializes the timestamp with the current time, in UTC. -// It returns an error if the operation fails. -func (ms *memStore) Reset(key string, win time.Duration) error { - ms.Lock() - defer ms.Unlock() - c := &counter{1, time.Now().UTC()} - if ms.keys != nil { - ms.keys.Add(key, c) - } else { - ms.m[key] = c - } - return nil -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go b/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go deleted file mode 100644 index e8ef8d0da..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go +++ /dev/null @@ -1,43 +0,0 @@ -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.go b/vendor/gopkg.in/throttled/throttled.v1/store/redis.go deleted file mode 100644 index b089f9f4e..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store/redis.go +++ /dev/null @@ -1,85 +0,0 @@ -package store - -import ( - "time" - - "github.com/garyburd/redigo/redis" - "gopkg.in/throttled/throttled.v1" -) - -// redisStore implements a Redis-based store. -type redisStore struct { - pool *redis.Pool - prefix string - db int -} - -// NewRedisStore creates a new Redis-based store, using the provided pool to get its -// connections. The keys will have the specified keyPrefix, which may be an empty string, -// and the database index specified by db will be selected to store the keys. -// -func NewRedisStore(pool *redis.Pool, keyPrefix string, db int) throttled.Store { - return &redisStore{ - pool: pool, - prefix: keyPrefix, - db: db, - } -} - -// Incr increments the specified key. If the key did not exist, it sets it to 1 -// and sets it to expire after the number of seconds specified by window. -// -// It returns the new count value and the number of remaining seconds, or an error -// if the operation fails. -func (r *redisStore) Incr(key string, window time.Duration) (int, int, error) { - conn := r.pool.Get() - defer conn.Close() - if err := selectDB(r.db, conn); err != nil { - return 0, 0, err - } - // Atomically increment and read the TTL. - conn.Send("MULTI") - conn.Send("INCR", r.prefix+key) - conn.Send("TTL", r.prefix+key) - vals, err := redis.Values(conn.Do("EXEC")) - if err != nil { - conn.Do("DISCARD") - return 0, 0, err - } - var cnt, ttl int - if _, err = redis.Scan(vals, &cnt, &ttl); err != nil { - return 0, 0, err - } - // If there was no TTL set, then this is a newly created key (INCR creates the key - // if it didn't exist), so set it to expire. - if ttl == -1 { - ttl = int(window.Seconds()) - _, err = conn.Do("EXPIRE", r.prefix+key, ttl) - if err != nil { - return 0, 0, err - } - } - return cnt, ttl, nil -} - -// Reset sets the value of the key to 1, and resets its time window. -func (r *redisStore) Reset(key string, window time.Duration) error { - conn := r.pool.Get() - defer conn.Close() - if err := selectDB(r.db, conn); err != nil { - return err - } - _, err := redis.String(conn.Do("SET", r.prefix+key, "1", "EX", int(window.Seconds()), "NX")) - return err -} - -// Select the specified database index. -func selectDB(db int, conn redis.Conn) error { - // Select the specified database - if db > 0 { - if _, err := redis.String(conn.Do("SELECT", db)); err != nil { - return err - } - } - return nil -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go b/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go deleted file mode 100644 index a282d6d25..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go +++ /dev/null @@ -1,66 +0,0 @@ -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/throttler.go b/vendor/gopkg.in/throttled/throttled.v1/throttler.go deleted file mode 100644 index 06da13051..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/throttler.go +++ /dev/null @@ -1,86 +0,0 @@ -package throttled - -import ( - "net/http" - "sync" -) - -var ( - // DefaultDeniedHandler handles the requests that were denied access because - // of a throttler. By default, returns a 429 status code with a - // generic message. - DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.Error(w, "limit exceeded", 429) - })) - - // Error is the function to call when an error occurs on a throttled handler. - // By default, returns a 500 status code with a generic message. - Error = ErrorFunc(func(w http.ResponseWriter, r *http.Request, err error) { - http.Error(w, "internal error", http.StatusInternalServerError) - }) -) - -// ErrorFunc defines the function type for the Error variable. -type ErrorFunc func(w http.ResponseWriter, r *http.Request, err error) - -// The Limiter interface defines the methods required to control access to a -// throttled handler. -type Limiter interface { - Start() - Limit(http.ResponseWriter, *http.Request) (<-chan bool, error) -} - -// Custom creates a Throttler using the provided Limiter implementation. -func Custom(l Limiter) *Throttler { - return &Throttler{ - limiter: l, - } -} - -// A Throttler controls access to HTTP handlers using a Limiter. -type Throttler struct { - // DeniedHandler is called if the request is disallowed. If it is nil, - // the DefaultDeniedHandler variable is used. - DeniedHandler http.Handler - - limiter Limiter - // The mutex protects the started flag - mu sync.Mutex - started bool -} - -// Throttle wraps a HTTP handler so that its access is controlled by -// the Throttler. It returns the Handler with the throttling logic. -func (t *Throttler) Throttle(h http.Handler) http.Handler { - dh := t.start() - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ch, err := t.limiter.Limit(w, r) - if err != nil { - Error(w, r, err) - return - } - ok := <-ch - if ok { - h.ServeHTTP(w, r) - } else { - dh.ServeHTTP(w, r) - } - }) -} - -// start starts the throttling and returns the effective denied handler to -// use for requests that were denied access. -func (t *Throttler) start() http.Handler { - t.mu.Lock() - defer t.mu.Unlock() - // Get the effective denied handler - dh := t.DeniedHandler - if dh == nil { - dh = DefaultDeniedHandler - } - if !t.started { - t.limiter.Start() - t.started = true - } - return dh -} diff --git a/vendor/gopkg.in/throttled/throttled.v2/.gitignore b/vendor/gopkg.in/throttled/throttled.v2/.gitignore new file mode 100644 index 000000000..96c4e10b7 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.swp +*.swo +*.test +*.out diff --git a/vendor/gopkg.in/throttled/throttled.v2/.travis.yml b/vendor/gopkg.in/throttled/throttled.v2/.travis.yml new file mode 100644 index 000000000..29225d7af --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/.travis.yml @@ -0,0 +1,24 @@ +sudo: false + +language: go + +go: + - 1.3 + - tip + +notifications: + email: false + +services: + - redis-server + +install: + - make get-deps + # Move to the gopkg location that rather than the default clone location + # otherwise Go 1.4+ complains about incorrect import paths. + - export GOPKG_DIR=$GOPATH/src/gopkg.in/throttled + - mkdir -p $GOPKG_DIR + - mv $TRAVIS_BUILD_DIR $GOPKG_DIR/throttled.v2 + - cd $GOPKG_DIR/throttled.v2 + +script: make diff --git a/vendor/gopkg.in/throttled/throttled.v1/LICENSE b/vendor/gopkg.in/throttled/throttled.v2/LICENSE index f9616483e..f9616483e 100644 --- a/vendor/gopkg.in/throttled/throttled.v1/LICENSE +++ b/vendor/gopkg.in/throttled/throttled.v2/LICENSE diff --git a/vendor/gopkg.in/throttled/throttled.v2/Makefile b/vendor/gopkg.in/throttled/throttled.v2/Makefile new file mode 100644 index 000000000..cfc235c31 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/Makefile @@ -0,0 +1,31 @@ + + +.PHONY: test test-cover bench lint get-deps .go-test .go-test-cover + +test: .go-test bench lint + +test-cover: .go-test-cover bench lint + +bench: + go test -race -bench=. -cpu=1,2,4 + +lint: + gofmt -l . +ifneq ($(TRAVIS_GO_VERSION),1.3) # go vet doesn't play nicely on 1.3 + go vet ./... +endif + which golint # Fail if golint doesn't exist + -golint . # Don't fail on golint warnings themselves + -golint store # Don't fail on golint warnings themselves + +get-deps: + go get github.com/garyburd/redigo/redis + go get github.com/hashicorp/golang-lru + go get github.com/golang/lint/golint + +.go-test: + go test ./... + +.go-test-cover: + go test -coverprofile=throttled.coverage.out . + go test -coverprofile=store.coverage.out ./store diff --git a/vendor/gopkg.in/throttled/throttled.v2/README.md b/vendor/gopkg.in/throttled/throttled.v2/README.md new file mode 100644 index 000000000..b18dcbcc6 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/README.md @@ -0,0 +1,85 @@ +# Throttled [![build status](https://secure.travis-ci.org/throttled/throttled.png)](https://travis-ci.org/throttled/throttled) [![GoDoc](https://godoc.org/gopkg.in/throttled/throttled.v2?status.png)](https://godoc.org/gopkg.in/throttled/throttled.v2) + +Package throttled implements rate limiting access to resources such as +HTTP endpoints. + +The 2.0.0 release made some major changes to the throttled API. If +this change broke your code in problematic ways or you wish a feature +of the old API had been retained, please open an issue. We don't +guarantee any particular changes but would like to hear more about +what our users need. Thanks! + +## Installation + +throttled uses gopkg.in for semantic versioning: +`go get gopkg.in/throttled/throttled.v2` + +As of July 27, 2015, the package is located under its own Github +organization. Please adjust your imports to +`gopkg.in/throttled/throttled.v2`. + +The 1.x release series is compatible with the original, unversioned +library written by [Martin Angers][puerkitobio]. There is a +[blog post explaining that version's usage on 0value.com][blog]. + +## Documentation + +API documentation is available on [godoc.org][doc]. The following +example demonstrates the usage of HTTPLimiter for rate-limiting access +to an http.Handler to 20 requests per path per minute with bursts of +up to 5 additional requests: + + store, err := memstore.New(65536) + if err != nil { + log.Fatal(err) + } + + quota := throttled.RateQuota{throttled.PerMin(20), 5} + rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) + if err != nil { + log.Fatal(err) + } + + httpRateLimiter := throttled.HTTPRateLimiter{ + RateLimiter: rateLimiter, + VaryBy: &throttled.VaryBy{Path: true}, + } + + http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler)) + +## Contributing + +Since throttled uses gopkg.in for versioning, running `go get` against +a fork or cloning from Github to the default path will break +imports. Instead, use the following process for setting up your +environment and contributing: + +```sh +# Retrieve the source and dependencies. +go get gopkg.in/throttled/throttled.v2/... + +# Fork the project on Github. For all following directions replace +# <username> with your Github username. Add your fork as a remote. +cd $GOPATH/src/gopkg.in/throttled/throttled.v2 +git remote add fork git@github.com:<username>/throttled.git + +# Create a branch, make your changes, test them and commit. +git checkout -b my-new-feature +# <do some work> +make test +git commit -a +git push -u fork my-new-feature +``` + +When your changes are ready, [open a pull request][pr] using "compare +across forks". + +## License + +The [BSD 3-clause license][bsd]. Copyright (c) 2014 Martin Angers and Contributors. + +[blog]: http://0value.com/throttled--guardian-of-the-web-server +[bsd]: https://opensource.org/licenses/BSD-3-Clause +[doc]: https://godoc.org/gopkg.in/throttled/throttled.v2 +[puerkitobio]: https://github.com/puerkitobio/ +[pr]: https://github.com/throttled/throttled/compare diff --git a/vendor/gopkg.in/throttled/throttled.v2/deprecated.go b/vendor/gopkg.in/throttled/throttled.v2/deprecated.go new file mode 100644 index 000000000..f2c648a3e --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/deprecated.go @@ -0,0 +1,73 @@ +package throttled + +import ( + "net/http" + "time" +) + +// DEPRECATED. Quota returns the number of requests allowed and the custom time window. +func (q Rate) Quota() (int, time.Duration) { + return q.count, q.period * time.Duration(q.count) +} + +// DEPRECATED. Q represents a custom quota. +type Q struct { + Requests int + Window time.Duration +} + +// DEPRECATED. Quota returns the number of requests allowed and the custom time window. +func (q Q) Quota() (int, time.Duration) { + return q.Requests, q.Window +} + +// DEPRECATED. The Quota interface defines the method to implement to describe +// a time-window quota, as required by the RateLimit throttler. +type Quota interface { + // Quota returns a number of requests allowed, and a duration. + Quota() (int, time.Duration) +} + +// DEPRECATED. Throttler is a backwards-compatible alias for HTTPLimiter. +type Throttler struct { + HTTPRateLimiter +} + +// DEPRECATED. Throttle is an alias for HTTPLimiter#Limit +func (t *Throttler) Throttle(h http.Handler) http.Handler { + return t.RateLimit(h) +} + +// DEPRECATED. RateLimit creates a Throttler that conforms to the given +// rate limits +func RateLimit(q Quota, vary *VaryBy, store GCRAStore) *Throttler { + count, period := q.Quota() + + if count < 1 { + count = 1 + } + if period <= 0 { + period = time.Second + } + + rate := Rate{period: period / time.Duration(count)} + limiter, err := NewGCRARateLimiter(store, RateQuota{rate, count - 1}) + + // This panic in unavoidable because the original interface does + // not support returning an error. + if err != nil { + panic(err) + } + + return &Throttler{ + HTTPRateLimiter{ + RateLimiter: limiter, + VaryBy: vary, + }, + } +} + +// DEPRECATED. Store is an alias for GCRAStore +type Store interface { + GCRAStore +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go b/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go new file mode 100644 index 000000000..93406648f --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go @@ -0,0 +1,59 @@ +package throttled_test + +import ( + "net/http" + "net/http/httptest" + "testing" + + "gopkg.in/throttled/throttled.v2" + "gopkg.in/throttled/throttled.v2/store" +) + +// Ensure that the current implementation remains compatible with the +// supported but deprecated usage until the next major version. +func TestDeprecatedUsage(t *testing.T) { + // Declare interfaces to statically check that names haven't changed + var st throttled.Store + var thr *throttled.Throttler + var q throttled.Quota + + st = store.NewMemStore(100) + vary := &throttled.VaryBy{Path: true} + q = throttled.PerMin(2) + thr = throttled.RateLimit(q, vary, st) + handler := thr.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + })) + + cases := []struct { + path string + code int + headers map[string]string + }{ + {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}}, + {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60"}}, + {"/foo", 429, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60", "Retry-After": "30"}}, + {"/bar", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}}, + } + + for i, c := range cases { + req, err := http.NewRequest("GET", c.path, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + handler.ServeHTTP(rr, req) + if have, want := rr.Code, c.code; have != want { + t.Errorf("Expected request %d at %s to return %d but got %d", + i, c.path, want, have) + } + + for name, want := range c.headers { + if have := rr.HeaderMap.Get(name); have != want { + t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'", + i, c.path, name, want, have) + } + } + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/doc.go b/vendor/gopkg.in/throttled/throttled.v2/doc.go new file mode 100644 index 000000000..302c2bed7 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/doc.go @@ -0,0 +1,3 @@ +// Package throttled implements rate limiting access to resources such +// as HTTP endpoints. +package throttled // import "gopkg.in/throttled/throttled.v2" diff --git a/vendor/gopkg.in/throttled/throttled.v2/example_test.go b/vendor/gopkg.in/throttled/throttled.v2/example_test.go new file mode 100644 index 000000000..66e6374be --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/example_test.go @@ -0,0 +1,103 @@ +package throttled_test + +import ( + "fmt" + "log" + "net/http" + + "gopkg.in/throttled/throttled.v2" + "gopkg.in/throttled/throttled.v2/store/memstore" +) + +var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("hi there!")) +}) + +// ExampleHTTPRateLimiter demonstrates the usage of HTTPRateLimiter +// for rate-limiting access to an http.Handler to 20 requests per path +// per minute with a maximum burst of 5 requests. +func ExampleHTTPRateLimiter() { + store, err := memstore.New(65536) + if err != nil { + log.Fatal(err) + } + + // Maximum burst of 5 which refills at 20 tokens per minute. + quota := throttled.RateQuota{throttled.PerMin(20), 5} + + rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) + if err != nil { + log.Fatal(err) + } + + httpRateLimiter := throttled.HTTPRateLimiter{ + RateLimiter: rateLimiter, + VaryBy: &throttled.VaryBy{Path: true}, + } + + http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler)) +} + +// Demonstrates direct use of GCRARateLimiter's RateLimit function (and the +// more general RateLimiter interface). This should be used anywhere where +// granular control over rate limiting is required. +func ExampleGCRARateLimiter() { + store, err := memstore.New(65536) + if err != nil { + log.Fatal(err) + } + + // Maximum burst of 5 which refills at 1 token per hour. + quota := throttled.RateQuota{throttled.PerHour(1), 5} + + rateLimiter, err := throttled.NewGCRARateLimiter(store, quota) + if err != nil { + log.Fatal(err) + } + + // Bucket according to the number i / 10 (so 1 falls into the bucket 0 + // while 11 falls into the bucket 1). This has the effect of allowing a + // burst of 5 plus 1 (a single emission interval) on every ten iterations + // of the loop. See the output for better clarity here. + // + // We also refill the bucket at 1 token per hour, but that has no effect + // for the purposes of this example. + for i := 0; i < 20; i++ { + bucket := fmt.Sprintf("by-order:%v", i/10) + + limited, result, err := rateLimiter.RateLimit(bucket, 1) + if err != nil { + log.Fatal(err) + } + + if limited { + fmt.Printf("Iteration %2v; bucket %v: FAILED. Rate limit exceeded.\n", + i, bucket) + } else { + fmt.Printf("Iteration %2v; bucket %v: Operation successful (remaining=%v).\n", + i, bucket, result.Remaining) + } + } + + // Output: + // Iteration 0; bucket by-order:0: Operation successful (remaining=5). + // Iteration 1; bucket by-order:0: Operation successful (remaining=4). + // Iteration 2; bucket by-order:0: Operation successful (remaining=3). + // Iteration 3; bucket by-order:0: Operation successful (remaining=2). + // Iteration 4; bucket by-order:0: Operation successful (remaining=1). + // Iteration 5; bucket by-order:0: Operation successful (remaining=0). + // Iteration 6; bucket by-order:0: FAILED. Rate limit exceeded. + // Iteration 7; bucket by-order:0: FAILED. Rate limit exceeded. + // Iteration 8; bucket by-order:0: FAILED. Rate limit exceeded. + // Iteration 9; bucket by-order:0: FAILED. Rate limit exceeded. + // Iteration 10; bucket by-order:1: Operation successful (remaining=5). + // Iteration 11; bucket by-order:1: Operation successful (remaining=4). + // Iteration 12; bucket by-order:1: Operation successful (remaining=3). + // Iteration 13; bucket by-order:1: Operation successful (remaining=2). + // Iteration 14; bucket by-order:1: Operation successful (remaining=1). + // Iteration 15; bucket by-order:1: Operation successful (remaining=0). + // Iteration 16; bucket by-order:1: FAILED. Rate limit exceeded. + // Iteration 17; bucket by-order:1: FAILED. Rate limit exceeded. + // Iteration 18; bucket by-order:1: FAILED. Rate limit exceeded. + // Iteration 19; bucket by-order:1: FAILED. Rate limit exceeded. +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/http.go b/vendor/gopkg.in/throttled/throttled.v2/http.go new file mode 100644 index 000000000..4c513a81d --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/http.go @@ -0,0 +1,110 @@ +package throttled + +import ( + "errors" + "math" + "net/http" + "strconv" +) + +var ( + // DefaultDeniedHandler is the default DeniedHandler for an + // HTTPRateLimiter. It returns a 429 status code with a generic + // message. + DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "limit exceeded", 429) + })) + + // DefaultError is the default Error function for an HTTPRateLimiter. + // It returns a 500 status code with a generic message. + DefaultError = func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, "internal error", http.StatusInternalServerError) + } +) + +// HTTPRateLimiter faciliates using a Limiter to limit HTTP requests. +type HTTPRateLimiter struct { + // DeniedHandler is called if the request is disallowed. If it is + // nil, the DefaultDeniedHandler variable is used. + DeniedHandler http.Handler + + // Error is called if the RateLimiter returns an error. If it is + // nil, the DefaultErrorFunc is used. + Error func(w http.ResponseWriter, r *http.Request, err error) + + // Limiter is call for each request to determine whether the + // request is permitted and update internal state. It must be set. + RateLimiter RateLimiter + + // VaryBy is called for each request to generate a key for the + // limiter. If it is nil, all requests use an empty string key. + VaryBy interface { + Key(*http.Request) string + } +} + +// RateLimit wraps an http.Handler to limit incoming requests. +// Requests that are not limited will be passed to the handler +// unchanged. Limited requests will be passed to the DeniedHandler. +// X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and +// Retry-After headers will be written to the response based on the +// values in the RateLimitResult. +func (t *HTTPRateLimiter) RateLimit(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if t.RateLimiter == nil { + t.error(w, r, errors.New("You must set a RateLimiter on HTTPRateLimiter")) + } + + var k string + if t.VaryBy != nil { + k = t.VaryBy.Key(r) + } + + limited, context, err := t.RateLimiter.RateLimit(k, 1) + + if err != nil { + t.error(w, r, err) + return + } + + setRateLimitHeaders(w, context) + + if !limited { + h.ServeHTTP(w, r) + } else { + dh := t.DeniedHandler + if dh == nil { + dh = DefaultDeniedHandler + } + dh.ServeHTTP(w, r) + } + }) +} + +func (t *HTTPRateLimiter) error(w http.ResponseWriter, r *http.Request, err error) { + e := t.Error + if e == nil { + e = DefaultError + } + e(w, r, err) +} + +func setRateLimitHeaders(w http.ResponseWriter, context RateLimitResult) { + if v := context.Limit; v >= 0 { + w.Header().Add("X-RateLimit-Limit", strconv.Itoa(v)) + } + + if v := context.Remaining; v >= 0 { + w.Header().Add("X-RateLimit-Remaining", strconv.Itoa(v)) + } + + if v := context.ResetAfter; v >= 0 { + vi := int(math.Ceil(v.Seconds())) + w.Header().Add("X-RateLimit-Reset", strconv.Itoa(vi)) + } + + if v := context.RetryAfter; v >= 0 { + vi := int(math.Ceil(v.Seconds())) + w.Header().Add("Retry-After", strconv.Itoa(vi)) + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/http_test.go b/vendor/gopkg.in/throttled/throttled.v2/http_test.go new file mode 100644 index 000000000..42761da09 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/http_test.go @@ -0,0 +1,99 @@ +package throttled_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "testing" + "time" + + "gopkg.in/throttled/throttled.v2" +) + +type stubLimiter struct { +} + +func (sl *stubLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error) { + switch key { + case "limit": + return true, throttled.RateLimitResult{-1, -1, -1, time.Minute}, nil + case "error": + return false, throttled.RateLimitResult{}, errors.New("stubLimiter error") + default: + return false, throttled.RateLimitResult{1, 2, time.Minute, -1}, nil + } +} + +type pathGetter struct{} + +func (*pathGetter) Key(r *http.Request) string { + return r.URL.Path +} + +type httpTestCase struct { + path string + code int + headers map[string]string +} + +func TestHTTPRateLimiter(t *testing.T) { + limiter := throttled.HTTPRateLimiter{ + RateLimiter: &stubLimiter{}, + VaryBy: &pathGetter{}, + } + + handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + })) + + runHTTPTestCases(t, handler, []httpTestCase{ + {"ok", 200, map[string]string{"X-Ratelimit-Limit": "1", "X-Ratelimit-Remaining": "2", "X-Ratelimit-Reset": "60"}}, + {"error", 500, map[string]string{}}, + {"limit", 429, map[string]string{"Retry-After": "60"}}, + }) +} + +func TestCustomHTTPRateLimiterHandlers(t *testing.T) { + limiter := throttled.HTTPRateLimiter{ + RateLimiter: &stubLimiter{}, + VaryBy: &pathGetter{}, + DeniedHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "custom limit exceeded", 400) + }), + Error: func(w http.ResponseWriter, r *http.Request, err error) { + http.Error(w, "custom internal error", 501) + }, + } + + handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + })) + + runHTTPTestCases(t, handler, []httpTestCase{ + {"limit", 400, map[string]string{}}, + {"error", 501, map[string]string{}}, + }) +} + +func runHTTPTestCases(t *testing.T, h http.Handler, cs []httpTestCase) { + for i, c := range cs { + req, err := http.NewRequest("GET", c.path, nil) + if err != nil { + t.Fatal(err) + } + + rr := httptest.NewRecorder() + h.ServeHTTP(rr, req) + if have, want := rr.Code, c.code; have != want { + t.Errorf("Expected request %d at %s to return %d but got %d", + i, c.path, want, have) + } + + for name, want := range c.headers { + if have := rr.HeaderMap.Get(name); have != want { + t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'", + i, c.path, name, want, have) + } + } + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/rate.go b/vendor/gopkg.in/throttled/throttled.v2/rate.go new file mode 100644 index 000000000..8c11cdb47 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/rate.go @@ -0,0 +1,239 @@ +package throttled + +import ( + "fmt" + "time" +) + +const ( + // Maximum number of times to retry SetIfNotExists/CompareAndSwap operations + // before returning an error. + maxCASAttempts = 10 +) + +// A RateLimiter manages limiting the rate of actions by key. +type RateLimiter interface { + // RateLimit checks whether a particular key has exceeded a rate + // limit. It also returns a RateLimitResult to provide additional + // information about the state of the RateLimiter. + // + // If the rate limit has not been exceeded, the underlying storage + // is updated by the supplied quantity. For example, a quantity of + // 1 might be used to rate limit a single request while a greater + // quantity could rate limit based on the size of a file upload in + // megabytes. If quantity is 0, no update is performed allowing + // you to "peek" at the state of the RateLimiter for a given key. + RateLimit(key string, quantity int) (bool, RateLimitResult, error) +} + +// RateLimitResult represents the state of the RateLimiter for a +// given key at the time of the query. This state can be used, for +// example, to communicate information to the client via HTTP +// headers. Negative values indicate that the attribute is not +// relevant to the implementation or state. +type RateLimitResult struct { + // Limit is the maximum number of requests that could be permitted + // instantaneously for this key starting from an empty state. For + // example, if a rate limiter allows 10 requests per second per + // key, Limit would always be 10. + Limit int + + // Remaining is the maximum number of requests that could be + // permitted instantaneously for this key given the current + // state. For example, if a rate limiter allows 10 requests per + // second and has already received 6 requests for this key this + // second, Remaining would be 4. + Remaining int + + // ResetAfter is the time until the RateLimiter returns to its + // initial state for a given key. For example, if a rate limiter + // manages requests per second and received one request 200ms ago, + // Reset would return 800ms. You can also think of this as the time + // until Limit and Remaining will be equal. + ResetAfter time.Duration + + // RetryAfter is the time until the next request will be permitted. + // It should be -1 unless the rate limit has been exceeded. + RetryAfter time.Duration +} + +type limitResult struct { + limited bool +} + +func (r *limitResult) Limited() bool { return r.limited } + +type rateLimitResult struct { + limitResult + + limit, remaining int + reset, retryAfter time.Duration +} + +func (r *rateLimitResult) Limit() int { return r.limit } +func (r *rateLimitResult) Remaining() int { return r.remaining } +func (r *rateLimitResult) Reset() time.Duration { return r.reset } +func (r *rateLimitResult) RetryAfter() time.Duration { return r.retryAfter } + +// Rate describes a frequency of an activity such as the number of requests +// allowed per minute. +type Rate struct { + period time.Duration // Time between equally spaced requests at the rate + count int // Used internally for deprecated `RateLimit` interface only +} + +// RateQuota describes the number of requests allowed per time period. +// MaxRate specified the maximum sustained rate of requests and must +// be greater than zero. MaxBurst defines the number of requests that +// will be allowed to exceed the rate in a single burst and must be +// greater than or equal to zero. +// +// Rate{PerSec(1), 0} would mean that after each request, no more +// requests will be permitted for that client for one second. In +// practice, you probably want to set MaxBurst >0 to provide some +// flexibility to clients that only need to make a handful of +// requests. In fact a MaxBurst of zero will *never* permit a request +// with a quantity greater than one because it will immediately exceed +// the limit. +type RateQuota struct { + MaxRate Rate + MaxBurst int +} + +// PerSec represents a number of requests per second. +func PerSec(n int) Rate { return Rate{time.Second / time.Duration(n), n} } + +// PerMin represents a number of requests per minute. +func PerMin(n int) Rate { return Rate{time.Minute / time.Duration(n), n} } + +// PerHour represents a number of requests per hour. +func PerHour(n int) Rate { return Rate{time.Hour / time.Duration(n), n} } + +// PerDay represents a number of requests per day. +func PerDay(n int) Rate { return Rate{24 * time.Hour / time.Duration(n), n} } + +// GCRARateLimiter is a RateLimiter that users the generic cell-rate +// algorithm. 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. +type GCRARateLimiter struct { + limit int + // Think of the DVT as our flexibility: + // How far can you deviate from the nominal equally spaced schedule? + // If you like leaky buckets, think about it as the size of your bucket. + delayVariationTolerance time.Duration + // Think of the emission interval as the time between events + // in the nominal equally spaced schedule. If you like leaky buckets, + // think of it as how frequently the bucket leaks one unit. + emissionInterval time.Duration + + store GCRAStore +} + +// NewGCRARateLimiter creates a GCRARateLimiter. quota.Count defines +// the maximum number of requests permitted in an instantaneous burst +// and quota.Count / quota.Period defines the maximum sustained +// rate. For example, PerMin(60) permits 60 requests instantly per key +// followed by one request per second indefinitely whereas PerSec(1) +// only permits one request per second with no tolerance for bursts. +func NewGCRARateLimiter(st GCRAStore, quota RateQuota) (*GCRARateLimiter, error) { + if quota.MaxBurst < 0 { + return nil, fmt.Errorf("Invalid RateQuota %#v. MaxBurst must be greater than zero.", quota) + } + if quota.MaxRate.period <= 0 { + return nil, fmt.Errorf("Invalid RateQuota %#v. MaxRate must be greater than zero.", quota) + } + + return &GCRARateLimiter{ + delayVariationTolerance: quota.MaxRate.period * (time.Duration(quota.MaxBurst) + 1), + emissionInterval: quota.MaxRate.period, + limit: quota.MaxBurst + 1, + store: st, + }, nil +} + +// RateLimit checks whether a particular key has exceeded a rate +// limit. It also returns a RateLimitResult to provide additional +// information about the state of the RateLimiter. +// +// If the rate limit has not been exceeded, the underlying storage is +// updated by the supplied quantity. For example, a quantity of 1 +// might be used to rate limit a single request while a greater +// quantity could rate limit based on the size of a file upload in +// megabytes. If quantity is 0, no update is performed allowing you +// to "peek" at the state of the RateLimiter for a given key. +func (g *GCRARateLimiter) RateLimit(key string, quantity int) (bool, RateLimitResult, error) { + var tat, newTat, now time.Time + var ttl time.Duration + rlc := RateLimitResult{Limit: g.limit, RetryAfter: -1} + limited := false + + i := 0 + for { + var err error + var tatVal int64 + var updated bool + + // tat refers to the theoretical arrival time that would be expected + // from equally spaced requests at exactly the rate limit. + tatVal, now, err = g.store.GetWithTime(key) + if err != nil { + return false, rlc, err + } + + if tatVal == -1 { + tat = now + } else { + tat = time.Unix(0, tatVal) + } + + increment := time.Duration(quantity) * g.emissionInterval + if now.After(tat) { + newTat = now.Add(increment) + } else { + newTat = tat.Add(increment) + } + + // Block the request if the next permitted time is in the future + allowAt := newTat.Add(-(g.delayVariationTolerance)) + if diff := now.Sub(allowAt); diff < 0 { + if increment <= g.delayVariationTolerance { + rlc.RetryAfter = -diff + } + ttl = tat.Sub(now) + limited = true + break + } + + ttl = newTat.Sub(now) + + if tatVal == -1 { + updated, err = g.store.SetIfNotExistsWithTTL(key, newTat.UnixNano(), ttl) + } else { + updated, err = g.store.CompareAndSwapWithTTL(key, tatVal, newTat.UnixNano(), ttl) + } + + if err != nil { + return false, rlc, err + } + if updated { + break + } + + i++ + if i > maxCASAttempts { + return false, rlc, fmt.Errorf( + "Failed to store updated rate limit data for key %s after %d attempts", + key, i, + ) + } + } + + next := g.delayVariationTolerance - ttl + if next > -g.emissionInterval { + rlc.Remaining = int(next / g.emissionInterval) + } + rlc.ResetAfter = ttl + + return limited, rlc, nil +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/rate_test.go b/vendor/gopkg.in/throttled/throttled.v2/rate_test.go new file mode 100644 index 000000000..292a050bc --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/rate_test.go @@ -0,0 +1,128 @@ +package throttled_test + +import ( + "testing" + "time" + + "gopkg.in/throttled/throttled.v2" + "gopkg.in/throttled/throttled.v2/store/memstore" +) + +const deniedStatus = 429 + +type testStore struct { + store throttled.GCRAStore + + clock time.Time + failUpdates bool +} + +func (ts *testStore) GetWithTime(key string) (int64, time.Time, error) { + v, _, e := ts.store.GetWithTime(key) + return v, ts.clock, e +} + +func (ts *testStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) { + if ts.failUpdates { + return false, nil + } + return ts.store.SetIfNotExistsWithTTL(key, value, ttl) +} + +func (ts *testStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) { + if ts.failUpdates { + return false, nil + } + return ts.store.CompareAndSwapWithTTL(key, old, new, ttl) +} + +func TestRateLimit(t *testing.T) { + limit := 5 + rq := throttled.RateQuota{throttled.PerSec(1), limit - 1} + start := time.Unix(0, 0) + cases := []struct { + now time.Time + volume, remaining int + reset, retry time.Duration + limited bool + }{ + // You can never make a request larger than the maximum + 0: {start, 6, 5, 0, -1, true}, + // Rate limit normal requests appropriately + 1: {start, 1, 4, time.Second, -1, false}, + 2: {start, 1, 3, 2 * time.Second, -1, false}, + 3: {start, 1, 2, 3 * time.Second, -1, false}, + 4: {start, 1, 1, 4 * time.Second, -1, false}, + 5: {start, 1, 0, 5 * time.Second, -1, false}, + 6: {start, 1, 0, 5 * time.Second, time.Second, true}, + 7: {start.Add(3000 * time.Millisecond), 1, 2, 3000 * time.Millisecond, -1, false}, + 8: {start.Add(3100 * time.Millisecond), 1, 1, 3900 * time.Millisecond, -1, false}, + 9: {start.Add(4000 * time.Millisecond), 1, 1, 4000 * time.Millisecond, -1, false}, + 10: {start.Add(8000 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false}, + 11: {start.Add(9500 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false}, + // Zero-volume request just peeks at the state + 12: {start.Add(9500 * time.Millisecond), 0, 4, time.Second, -1, false}, + // High-volume request uses up more of the limit + 13: {start.Add(9500 * time.Millisecond), 2, 2, 3 * time.Second, -1, false}, + // Large requests cannot exceed limits + 14: {start.Add(9500 * time.Millisecond), 5, 2, 3 * time.Second, 3 * time.Second, true}, + } + + mst, err := memstore.New(0) + if err != nil { + t.Fatal(err) + } + st := testStore{store: mst} + + rl, err := throttled.NewGCRARateLimiter(&st, rq) + if err != nil { + t.Fatal(err) + } + + // Start the server + for i, c := range cases { + st.clock = c.now + + limited, context, err := rl.RateLimit("foo", c.volume) + if err != nil { + t.Fatalf("%d: %#v", i, err) + } + + if limited != c.limited { + t.Errorf("%d: expected Limited to be %t but got %t", i, c.limited, limited) + } + + if have, want := context.Limit, limit; have != want { + t.Errorf("%d: expected Limit to be %d but got %d", i, want, have) + } + + if have, want := context.Remaining, c.remaining; have != want { + t.Errorf("%d: expected Remaining to be %d but got %d", i, want, have) + } + + if have, want := context.ResetAfter, c.reset; have != want { + t.Errorf("%d: expected ResetAfter to be %s but got %s", i, want, have) + } + + if have, want := context.RetryAfter, c.retry; have != want { + t.Errorf("%d: expected RetryAfter to be %d but got %d", i, want, have) + } + } +} + +func TestRateLimitUpdateFailures(t *testing.T) { + rq := throttled.RateQuota{throttled.PerSec(1), 1} + mst, err := memstore.New(0) + if err != nil { + t.Fatal(err) + } + st := testStore{store: mst, failUpdates: true} + rl, err := throttled.NewGCRARateLimiter(&st, rq) + if err != nil { + t.Fatal(err) + } + + if _, _, err := rl.RateLimit("foo", 1); err == nil { + t.Error("Expected limiting to fail when store updates fail") + } +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store.go b/vendor/gopkg.in/throttled/throttled.v2/store.go new file mode 100644 index 000000000..a26bbc2c5 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store.go @@ -0,0 +1,34 @@ +package throttled + +import ( + "time" +) + +// GCRAStore is the interface to implement to store state for a GCRA +// rate limiter +type GCRAStore interface { + // GetWithTime returns the value of the key if it is in the store + // or -1 if it does not exist. It also returns the current time at + // the Store. The time must be representable as a positive int64 + // of nanoseconds since the epoch. + // + // GCRA assumes that all instances sharing the same Store also + // share the same clock. Using separate clocks will work if the + // skew is small but not recommended in practice unless you're + // lucky enough to be hooked up to GPS or atomic clocks. + GetWithTime(key string) (int64, time.Time, error) + + // SetIfNotExistsWithTTL sets the value of key only if it is not + // already set in the store it returns whether a new value was + // set. If the store supports expiring keys and a new value was + // set, the key will expire after the provided ttl. + SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) + + // CompareAndSwapWithTTL atomically compares the value at key to + // the old value. If it matches, it sets it to the new value and + // returns true. Otherwise, it returns false. If the key does not + // exist in the store, it returns false with no error. If the + // store supports expiring keys and the swap succeeded, the key + // will expire after the provided ttl. + CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go b/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go new file mode 100644 index 000000000..5476e87ac --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go @@ -0,0 +1,32 @@ +// Package store contains deprecated aliases for subpackages +package store // import "gopkg.in/throttled/throttled.v2/store" + +import ( + "github.com/garyburd/redigo/redis" + + "gopkg.in/throttled/throttled.v2/store/memstore" + "gopkg.in/throttled/throttled.v2/store/redigostore" +) + +// DEPRECATED. NewMemStore is a compatible alias for mem.New +func NewMemStore(maxKeys int) *memstore.MemStore { + st, err := memstore.New(maxKeys) + if err != nil { + // As of this writing, `lru.New` can only return an error if you pass + // maxKeys <= 0 so this should never occur. + panic(err) + } + return st +} + +// DEPRECATED. NewMemStore is a compatible alias for redis.New +func NewRedisStore(pool *redis.Pool, keyPrefix string, db int) *redigostore.RedigoStore { + st, err := redigostore.New(pool, keyPrefix, db) + if err != nil { + // As of this writing, creating a Redis store never returns an error + // so this should be safe while providing some ability to return errors + // in the future. + panic(err) + } + return st +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go new file mode 100644 index 000000000..5d8fee8b5 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go @@ -0,0 +1,127 @@ +// Package memstore offers an in-memory store implementation for throttled. +package memstore // import "gopkg.in/throttled/throttled.v2/store/memstore" + +import ( + "sync" + "sync/atomic" + "time" + + "github.com/hashicorp/golang-lru" +) + +// MemStore is an in-memory store implementation for throttled. It +// supports evicting the least recently used keys to control memory +// usage. It is stored in memory in the current process and thus +// doesn't share state with other rate limiters. +type MemStore struct { + sync.RWMutex + keys *lru.Cache + m map[string]*int64 +} + +// New initializes a Store. If maxKeys > 0, the number of different +// keys is restricted to the specified amount. In this case, it uses +// an LRU algorithm to evict older keys to make room for newer +// ones. If maxKeys <= 0, there is no limit on the number of keys, +// which may use an unbounded amount of memory. +func New(maxKeys int) (*MemStore, error) { + var m *MemStore + + if maxKeys > 0 { + keys, err := lru.New(maxKeys) + if err != nil { + return nil, err + } + + m = &MemStore{ + keys: keys, + } + } else { + m = &MemStore{ + m: make(map[string]*int64), + } + } + return m, nil +} + +// GetWithTime returns the value of the key if it is in the store or +// -1 if it does not exist. It also returns the current local time on +// the machine. +func (ms *MemStore) GetWithTime(key string) (int64, time.Time, error) { + now := time.Now() + valP, ok := ms.get(key, false) + + if !ok { + return -1, now, nil + } + + return atomic.LoadInt64(valP), now, nil +} + +// SetIfNotExistsWithTTL sets the value of key only if it is not +// already set in the store it returns whether a new value was set. It +// ignores the ttl. +func (ms *MemStore) SetIfNotExistsWithTTL(key string, value int64, _ time.Duration) (bool, error) { + _, ok := ms.get(key, false) + + if ok { + return false, nil + } + + ms.Lock() + defer ms.Unlock() + + _, ok = ms.get(key, true) + + if ok { + return false, nil + } + + // Store a pointer to a new instance so that the caller + // can't mutate the value after setting + v := value + + if ms.keys != nil { + ms.keys.Add(key, &v) + } else { + ms.m[key] = &v + } + + return true, nil +} + +// CompareAndSwapWithTTL atomically compares the value at key to the +// old value. If it matches, it sets it to the new value and returns +// true. Otherwise, it returns false. If the key does not exist in the +// store, it returns false with no error. It ignores the ttl. +func (ms *MemStore) CompareAndSwapWithTTL(key string, old, new int64, _ time.Duration) (bool, error) { + valP, ok := ms.get(key, false) + + if !ok { + return false, nil + } + + return atomic.CompareAndSwapInt64(valP, old, new), nil +} + +func (ms *MemStore) get(key string, locked bool) (*int64, bool) { + var valP *int64 + var ok bool + + if ms.keys != nil { + var valI interface{} + + valI, ok = ms.keys.Get(key) + if ok { + valP = valI.(*int64) + } + } else { + if !locked { + ms.RLock() + defer ms.RUnlock() + } + valP, ok = ms.m[key] + } + + return valP, ok +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go new file mode 100644 index 000000000..ef003d3de --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go @@ -0,0 +1,40 @@ +package memstore_test + +import ( + "testing" + + "gopkg.in/throttled/throttled.v2/store/memstore" + "gopkg.in/throttled/throttled.v2/store/storetest" +) + +func TestMemStoreLRU(t *testing.T) { + st, err := memstore.New(10) + if err != nil { + t.Fatal(err) + } + storetest.TestGCRAStore(t, st) +} + +func TestMemStoreUnlimited(t *testing.T) { + st, err := memstore.New(10) + if err != nil { + t.Fatal(err) + } + storetest.TestGCRAStore(t, st) +} + +func BenchmarkMemStoreLRU(b *testing.B) { + st, err := memstore.New(10) + if err != nil { + b.Fatal(err) + } + storetest.BenchmarkGCRAStore(b, st) +} + +func BenchmarkMemStoreUnlimited(b *testing.B) { + st, err := memstore.New(0) + if err != nil { + b.Fatal(err) + } + storetest.BenchmarkGCRAStore(b, st) +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go new file mode 100644 index 000000000..54208fa6d --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go @@ -0,0 +1,156 @@ +// Package redigostore offers Redis-based store implementation for throttled using redigo. +package redigostore // import "gopkg.in/throttled/throttled.v2/store/redigostore" + +import ( + "strings" + "time" + + "github.com/garyburd/redigo/redis" +) + +const ( + redisCASMissingKey = "key does not exist" + redisCASScript = ` +local v = redis.call('get', KEYS[1]) +if v == false then + return redis.error_reply("key does not exist") +end +if v ~= ARGV[1] then + return 0 +end +if ARGV[3] ~= "0" then + redis.call('setex', KEYS[1], ARGV[3], ARGV[2]) +else + redis.call('set', KEYS[1], ARGV[2]) +end +return 1 +` +) + +// RedigoStore implements a Redis-based store using redigo. +type RedigoStore struct { + pool *redis.Pool + prefix string + db int +} + +// New creates a new Redis-based store, using the provided pool to get +// its connections. The keys will have the specified keyPrefix, which +// may be an empty string, and the database index specified by db will +// be selected to store the keys. Any updating operations will reset +// the key TTL to the provided value rounded down to the nearest +// second. Depends on Redis 2.6+ for EVAL support. +func New(pool *redis.Pool, keyPrefix string, db int) (*RedigoStore, error) { + return &RedigoStore{ + pool: pool, + prefix: keyPrefix, + db: db, + }, nil +} + +// GetWithTime returns the value of the key if it is in the store +// or -1 if it does not exist. It also returns the current time at +// the redis server to microsecond precision. +func (r *RedigoStore) GetWithTime(key string) (int64, time.Time, error) { + var now time.Time + + key = r.prefix + key + + conn, err := r.getConn() + if err != nil { + return 0, now, err + } + defer conn.Close() + + conn.Send("TIME") + conn.Send("GET", key) + conn.Flush() + timeReply, err := redis.Values(conn.Receive()) + if err != nil { + return 0, now, err + } + + var s, us int64 + if _, err := redis.Scan(timeReply, &s, &us); err != nil { + return 0, now, err + } + now = time.Unix(s, us*int64(time.Microsecond)) + + v, err := redis.Int64(conn.Receive()) + if err == redis.ErrNil { + return -1, now, nil + } else if err != nil { + return 0, now, err + } + + return v, now, nil +} + +// SetIfNotExistsWithTTL sets the value of key only if it is not +// already set in the store it returns whether a new value was set. +// If a new value was set, the ttl in the key is also set, though this +// operation is not performed atomically. +func (r *RedigoStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) { + key = r.prefix + key + + conn, err := r.getConn() + if err != nil { + return false, err + } + defer conn.Close() + + v, err := redis.Int64(conn.Do("SETNX", key, value)) + if err != nil { + return false, err + } + + updated := v == 1 + + if ttl >= time.Second { + if _, err := conn.Do("EXPIRE", key, int(ttl.Seconds())); err != nil { + return updated, err + } + } + + return updated, nil +} + +// CompareAndSwapWithTTL atomically compares the value at key to the +// old value. If it matches, it sets it to the new value and returns +// true. Otherwise, it returns false. If the key does not exist in the +// store, it returns false with no error. If the swap succeeds, the +// ttl for the key is updated atomically. +func (r *RedigoStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) { + key = r.prefix + key + conn, err := r.getConn() + if err != nil { + return false, err + } + defer conn.Close() + + swapped, err := redis.Bool(conn.Do("EVAL", redisCASScript, 1, key, old, new, int(ttl.Seconds()))) + if err != nil { + if strings.Contains(err.Error(), redisCASMissingKey) { + return false, nil + } + + return false, err + } + + return swapped, nil +} + +// Select the specified database index. +func (r *RedigoStore) getConn() (redis.Conn, error) { + conn := r.pool.Get() + + // Select the specified database + if r.db > 0 { + if _, err := redis.String(conn.Do("SELECT", r.db)); err != nil { + conn.Close() + return nil, err + } + } + + return conn, nil +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go new file mode 100644 index 000000000..d47b635d2 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go @@ -0,0 +1,85 @@ +package redigostore_test + +import ( + "testing" + "time" + + "github.com/garyburd/redigo/redis" + + "gopkg.in/throttled/throttled.v2/store/redigostore" + "gopkg.in/throttled/throttled.v2/store/storetest" +) + +const ( + redisTestDB = 1 + redisTestPrefix = "throttled:" +) + +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) { + c, st := setupRedis(t, 0) + defer c.Close() + defer clearRedis(c) + + clearRedis(c) + storetest.TestGCRAStore(t, st) + storetest.TestGCRAStoreTTL(t, st) +} + +func BenchmarkRedisStore(b *testing.B) { + c, st := setupRedis(b, 0) + defer c.Close() + defer clearRedis(c) + + storetest.BenchmarkGCRAStore(b, st) +} + +func clearRedis(c redis.Conn) error { + keys, err := redis.Values(c.Do("KEYS", redisTestPrefix+"*")) + if err != nil { + return err + } + + if _, err := redis.Int(c.Do("DEL", keys...)); err != nil { + return err + } + + return nil +} + +func setupRedis(tb testing.TB, ttl time.Duration) (redis.Conn, *redigostore.RedigoStore) { + pool := getPool() + c := pool.Get() + + if _, err := redis.String(c.Do("PING")); err != nil { + c.Close() + tb.Skip("redis server not available on localhost port 6379") + } + + if _, err := redis.String(c.Do("SELECT", redisTestDB)); err != nil { + c.Close() + tb.Fatal(err) + } + + st, err := redigostore.New(pool, redisTestPrefix, redisTestDB) + if err != nil { + c.Close() + tb.Fatal(err) + } + + return c, st +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go new file mode 100644 index 000000000..ecfee2638 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go @@ -0,0 +1,2 @@ +// Package storetest provides a helper for testing throttled stores. +package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest" diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go new file mode 100644 index 000000000..191b40a4f --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go @@ -0,0 +1,176 @@ +// Package storetest provides a helper for testing throttled stores. +package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest" + +import ( + "math/rand" + "strconv" + "sync/atomic" + "testing" + "time" + + "gopkg.in/throttled/throttled.v2" +) + +// TestGCRAStore tests the behavior of a GCRAStore implementation for +// compliance with the throttled API. It does not require support +// for TTLs. +func TestGCRAStore(t *testing.T, st throttled.GCRAStore) { + // GetWithTime a missing key + if have, _, err := st.GetWithTime("foo"); err != nil { + t.Fatal(err) + } else if have != -1 { + t.Errorf("expected GetWithTime to return -1 for a missing key but got %d", have) + } + + // SetIfNotExists on a new key + want := int64(1) + + if set, err := st.SetIfNotExistsWithTTL("foo", want, 0); err != nil { + t.Fatal(err) + } else if !set { + t.Errorf("expected SetIfNotExists on an empty key to succeed") + } + + before := time.Now() + + if have, now, err := st.GetWithTime("foo"); err != nil { + t.Fatal(err) + } else if have != want { + t.Errorf("expected GetWithTime to return %d but got %d", want, have) + } else if now.UnixNano() <= 0 { + t.Errorf("expected GetWithTime to return a time representable representable as a positive int64 of nanoseconds since the epoch") + } else if now.Before(before) || now.After(time.Now()) { + // Note that we make the assumption here that the store is running on + // the same machine as this test and thus shares a clock. This can be a + // little tricky in the case of Redis, which could be running + // elsewhere. The test assumes that it's running either locally on on + // Travis (where currently the Redis is available on localhost). If new + // test environments are procured, this may need to be revisited. + t.Errorf("expected GetWithTime to return a time between the time before the call and the time after the call") + } + + // SetIfNotExists on an existing key + if set, err := st.SetIfNotExistsWithTTL("foo", 123, 0); err != nil { + t.Fatal(err) + } else if set { + t.Errorf("expected SetIfNotExists on an existing key to fail") + } + + if have, _, err := st.GetWithTime("foo"); err != nil { + t.Fatal(err) + } else if have != want { + t.Errorf("expected GetWithTime to return %d but got %d", want, have) + } + + // SetIfNotExists on a different key + if set, err := st.SetIfNotExistsWithTTL("bar", 456, 0); err != nil { + t.Fatal(err) + } else if !set { + t.Errorf("expected SetIfNotExists on an empty key to succeed") + } + + // Returns the false on a missing key + if swapped, err := st.CompareAndSwapWithTTL("baz", 1, 2, 0); err != nil { + t.Fatal(err) + } else if swapped { + t.Errorf("expected CompareAndSwap to fail on a missing key") + } + + // Test a successful CAS + want = int64(2) + + if swapped, err := st.CompareAndSwapWithTTL("foo", 1, want, 0); err != nil { + t.Fatal(err) + } else if !swapped { + t.Errorf("expected CompareAndSwap to succeed") + } + + if have, _, err := st.GetWithTime("foo"); err != nil { + t.Fatal(err) + } else if have != want { + t.Errorf("expected GetWithTime to return %d but got %d", want, have) + } + + // Test an unsuccessful CAS + if swapped, err := st.CompareAndSwapWithTTL("foo", 1, 2, 0); err != nil { + t.Fatal(err) + } else if swapped { + t.Errorf("expected CompareAndSwap to fail") + } + + if have, _, err := st.GetWithTime("foo"); err != nil { + t.Fatal(err) + } else if have != want { + t.Errorf("expected GetWithTime to return %d but got %d", want, have) + } +} + +// TestGCRAStoreTTL tests the behavior of TTLs in a GCRAStore implementation. +func TestGCRAStoreTTL(t *testing.T, st throttled.GCRAStore) { + ttl := time.Second + want := int64(1) + key := "ttl" + + if _, err := st.SetIfNotExistsWithTTL(key, want, ttl); err != nil { + t.Fatal(err) + } + + if have, _, err := st.GetWithTime(key); err != nil { + t.Fatal(err) + } else if have != want { + t.Errorf("expected GetWithTime to return %d, got %d", want, have) + } + + // I can't think of a generic way to test expiration without a sleep + time.Sleep(ttl + time.Millisecond) + + if have, _, err := st.GetWithTime(key); err != nil { + t.Fatal(err) + } else if have != -1 { + t.Errorf("expected GetWithTime to fail on an expired key but got %d", have) + } +} + +// BenchmarkGCRAStore runs parallel benchmarks against a GCRAStore implementation. +// Aside from being useful for performance testing, this is useful for finding +// race conditions with the Go race detector. +func BenchmarkGCRAStore(b *testing.B, st throttled.GCRAStore) { + seed := int64(42) + var attempts, updates int64 + + b.RunParallel(func(pb *testing.PB) { + // We need atomic behavior around the RNG or go detects a race in the test + delta := int64(1) + seedValue := atomic.AddInt64(&seed, delta) - delta + gen := rand.New(rand.NewSource(seedValue)) + + for pb.Next() { + key := strconv.FormatInt(gen.Int63n(50), 10) + + var v int64 + var updated bool + + v, _, err := st.GetWithTime(key) + if v == -1 { + updated, err = st.SetIfNotExistsWithTTL(key, gen.Int63(), 0) + if err != nil { + b.Error(err) + } + } else if err != nil { + b.Error(err) + } else { + updated, err = st.CompareAndSwapWithTTL(key, v, gen.Int63(), 0) + if err != nil { + b.Error(err) + } + } + + atomic.AddInt64(&attempts, 1) + if updated { + atomic.AddInt64(&updates, 1) + } + } + }) + + b.Logf("%d/%d update operations succeeed", updates, attempts) +} diff --git a/vendor/gopkg.in/throttled/throttled.v1/varyby.go b/vendor/gopkg.in/throttled/throttled.v2/varyby.go index 3b2cdb011..dc6a01718 100644 --- a/vendor/gopkg.in/throttled/throttled.v1/varyby.go +++ b/vendor/gopkg.in/throttled/throttled.v2/varyby.go @@ -31,7 +31,7 @@ type VaryBy struct { // Defaults to a newline character if empty (\n). Separator string - // Custom specifies the custom-generated key to use for this request. + // DEPRECATED. Custom specifies the custom-generated key to use for this request. // If not nil, the value returned by this function is used instead of any // VaryBy criteria. Custom func(r *http.Request) string diff --git a/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go index 91b7ae0ae..66a5f4e98 100644 --- a/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go +++ b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go @@ -1,9 +1,11 @@ -package throttled +package throttled_test import ( "net/http" "net/url" "testing" + + "gopkg.in/throttled/throttled.v2" ) func TestVaryBy(t *testing.T) { @@ -13,34 +15,34 @@ func TestVaryBy(t *testing.T) { } ck := &http.Cookie{Name: "ssn", Value: "test"} cases := []struct { - vb *VaryBy + vb *throttled.VaryBy r *http.Request k string }{ 0: {nil, &http.Request{}, ""}, - 1: {&VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"}, + 1: {&throttled.VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"}, 2: { - &VaryBy{Method: true, Path: true}, + &throttled.VaryBy{Method: true, Path: true}, &http.Request{Method: "POST", URL: u}, "post\n/test/path\n", }, 3: { - &VaryBy{Headers: []string{"Content-length"}}, + &throttled.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"}}, + &throttled.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"}}, + &throttled.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 { + &throttled.VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string { return "blah" }}, &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, |