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