From 2ca0e8f9a0f9863555a26e984cde15efff9ef8f8 Mon Sep 17 00:00:00 2001 From: Christopher Speller Date: Fri, 23 Sep 2016 10:17:51 -0400 Subject: Updating golang dependancies (#4075) --- vendor/gopkg.in/fsnotify.v1/AUTHORS | 1 + vendor/gopkg.in/fsnotify.v1/CHANGELOG.md | 4 + vendor/gopkg.in/fsnotify.v1/windows.go | 8 +- vendor/gopkg.in/throttled/throttled.v1/.gitignore | 10 - vendor/gopkg.in/throttled/throttled.v1/.travis.yml | 10 - vendor/gopkg.in/throttled/throttled.v1/LICENSE | 12 -- vendor/gopkg.in/throttled/throttled.v1/README.md | 82 ------- .../gopkg.in/throttled/throttled.v1/common_test.go | 65 ------ vendor/gopkg.in/throttled/throttled.v1/delayer.go | 109 ---------- .../throttled/throttled.v1/delayer_test.go | 65 ------ vendor/gopkg.in/throttled/throttled.v1/doc.go | 77 ------- .../throttled/throttled.v1/examples/README.md | 12 -- .../throttled/throttled.v1/examples/custom/main.go | 90 -------- .../throttled.v1/examples/interval-many/main.go | 79 ------- .../throttled.v1/examples/interval-vary/main.go | 74 ------- .../throttled.v1/examples/interval-vary/siege-urls | 4 - .../throttled.v1/examples/interval/main.go | 69 ------ .../throttled.v1/examples/memstats/main.go | 97 --------- .../throttled.v1/examples/memstats/test-file | Bin 65536 -> 0 bytes .../throttled.v1/examples/rate-limit/main.go | 101 --------- vendor/gopkg.in/throttled/throttled.v1/interval.go | 164 -------------- .../throttled/throttled.v1/interval_test.go | 114 ---------- vendor/gopkg.in/throttled/throttled.v1/memstats.go | 214 ------------------ .../throttled/throttled.v1/memstats_test.go | 64 ------ .../throttled/throttled.v1/misc/pre-commit | 38 ---- vendor/gopkg.in/throttled/throttled.v1/rate.go | 116 ---------- .../gopkg.in/throttled/throttled.v1/rate_test.go | 101 --------- vendor/gopkg.in/throttled/throttled.v1/store.go | 31 --- .../gopkg.in/throttled/throttled.v1/store/doc.go | 2 - .../gopkg.in/throttled/throttled.v1/store/mem.go | 90 -------- .../throttled/throttled.v1/store/mem_test.go | 43 ---- .../gopkg.in/throttled/throttled.v1/store/redis.go | 85 -------- .../throttled/throttled.v1/store/redis_test.go | 66 ------ .../gopkg.in/throttled/throttled.v1/throttler.go | 86 -------- vendor/gopkg.in/throttled/throttled.v1/varyby.go | 78 ------- .../gopkg.in/throttled/throttled.v1/varyby_test.go | 56 ----- vendor/gopkg.in/throttled/throttled.v2/.gitignore | 5 + vendor/gopkg.in/throttled/throttled.v2/.travis.yml | 24 +++ vendor/gopkg.in/throttled/throttled.v2/LICENSE | 12 ++ vendor/gopkg.in/throttled/throttled.v2/Makefile | 31 +++ vendor/gopkg.in/throttled/throttled.v2/README.md | 85 ++++++++ .../gopkg.in/throttled/throttled.v2/deprecated.go | 73 +++++++ .../throttled/throttled.v2/deprecated_test.go | 59 +++++ vendor/gopkg.in/throttled/throttled.v2/doc.go | 3 + .../throttled/throttled.v2/example_test.go | 103 +++++++++ vendor/gopkg.in/throttled/throttled.v2/http.go | 110 ++++++++++ .../gopkg.in/throttled/throttled.v2/http_test.go | 99 +++++++++ vendor/gopkg.in/throttled/throttled.v2/rate.go | 239 +++++++++++++++++++++ .../gopkg.in/throttled/throttled.v2/rate_test.go | 128 +++++++++++ vendor/gopkg.in/throttled/throttled.v2/store.go | 34 +++ .../throttled/throttled.v2/store/deprecated.go | 32 +++ .../throttled.v2/store/memstore/memstore.go | 127 +++++++++++ .../throttled.v2/store/memstore/memstore_test.go | 40 ++++ .../throttled.v2/store/redigostore/redigostore.go | 156 ++++++++++++++ .../store/redigostore/redisstore_test.go | 85 ++++++++ .../throttled/throttled.v2/store/storetest/doc.go | 2 + .../throttled.v2/store/storetest/storetest.go | 176 +++++++++++++++ vendor/gopkg.in/throttled/throttled.v2/varyby.go | 78 +++++++ .../gopkg.in/throttled/throttled.v2/varyby_test.go | 58 +++++ vendor/gopkg.in/yaml.v2/LICENSE | 195 +---------------- vendor/gopkg.in/yaml.v2/README.md | 2 +- 61 files changed, 1779 insertions(+), 2494 deletions(-) delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/.gitignore delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/.travis.yml delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/LICENSE delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/README.md delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/common_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/delayer.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/delayer_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/doc.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/README.md delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/custom/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/interval-many/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/interval-vary/siege-urls delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/interval/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/memstats/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/examples/rate-limit/main.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/interval.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/interval_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/memstats.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/memstats_test.go delete mode 100755 vendor/gopkg.in/throttled/throttled.v1/misc/pre-commit delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/rate.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/rate_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store/doc.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store/mem.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store/mem_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store/redis.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/store/redis_test.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/throttler.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/varyby.go delete mode 100644 vendor/gopkg.in/throttled/throttled.v1/varyby_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/.gitignore create mode 100644 vendor/gopkg.in/throttled/throttled.v2/.travis.yml create mode 100644 vendor/gopkg.in/throttled/throttled.v2/LICENSE create mode 100644 vendor/gopkg.in/throttled/throttled.v2/Makefile create mode 100644 vendor/gopkg.in/throttled/throttled.v2/README.md create mode 100644 vendor/gopkg.in/throttled/throttled.v2/deprecated.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/doc.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/example_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/http.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/http_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/rate.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/rate_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/varyby.go create mode 100644 vendor/gopkg.in/throttled/throttled.v2/varyby_test.go (limited to 'vendor/gopkg.in') diff --git a/vendor/gopkg.in/fsnotify.v1/AUTHORS b/vendor/gopkg.in/fsnotify.v1/AUTHORS index 6438bb31c..71c47ce89 100644 --- a/vendor/gopkg.in/fsnotify.v1/AUTHORS +++ b/vendor/gopkg.in/fsnotify.v1/AUTHORS @@ -11,6 +11,7 @@ Adrien Bustany Amit Krishnan Bjørn Erik Pedersen +Bruno Bigras Caleb Spare Case Nelson Chris Howey diff --git a/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md b/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md index 675fab9eb..f6c7c485c 100644 --- a/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md +++ b/vendor/gopkg.in/fsnotify.v1/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v1.3.1 / 2016-06-28 + +* windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc) + ## v1.3.0 / 2016-04-19 * Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135) diff --git a/vendor/gopkg.in/fsnotify.v1/windows.go b/vendor/gopkg.in/fsnotify.v1/windows.go index c836bdb3d..09436f31d 100644 --- a/vendor/gopkg.in/fsnotify.v1/windows.go +++ b/vendor/gopkg.in/fsnotify.v1/windows.go @@ -306,7 +306,7 @@ func (w *Watcher) remWatch(pathname string) error { watch.mask = 0 } else { name := filepath.Base(pathname) - w.sendEvent(watch.path+"\\"+name, watch.names[name]&sysFSIGNORED) + w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED) delete(watch.names, name) } return w.startRead(watch) @@ -316,7 +316,7 @@ func (w *Watcher) remWatch(pathname string) error { func (w *Watcher) deleteWatch(watch *watch) { for name, mask := range watch.names { if mask&provisional == 0 { - w.sendEvent(watch.path+"\\"+name, mask&sysFSIGNORED) + w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED) } delete(watch.names, name) } @@ -453,7 +453,7 @@ func (w *Watcher) readEvents() { raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) - fullname := watch.path + "\\" + name + fullname := filepath.Join(watch.path, name) var mask uint64 switch raw.Action { @@ -491,7 +491,7 @@ func (w *Watcher) readEvents() { } } if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { - fullname = watch.path + "\\" + watch.rename + fullname = filepath.Join(watch.path, watch.rename) sendNameEvent() } 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/LICENSE b/vendor/gopkg.in/throttled/throttled.v1/LICENSE deleted file mode 100644 index f9616483e..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2014, Martin Angers and Contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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 deleted file mode 100644 index c97c12f9b..000000000 Binary files a/vendor/gopkg.in/throttled/throttled.v1/examples/memstats/test-file and /dev/null 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 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.v1/varyby.go b/vendor/gopkg.in/throttled/throttled.v1/varyby.go deleted file mode 100644 index 3b2cdb011..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/varyby.go +++ /dev/null @@ -1,78 +0,0 @@ -package throttled - -import ( - "bytes" - "net/http" - "strings" -) - -// VaryBy defines the criteria to use to group requests. -type VaryBy struct { - // Vary by the RemoteAddr as specified by the net/http.Request field. - RemoteAddr bool - - // Vary by the HTTP Method as specified by the net/http.Request field. - Method bool - - // Vary by the URL's Path as specified by the Path field of the net/http.Request - // URL field. - Path bool - - // Vary by this list of header names, read from the net/http.Request Header field. - Headers []string - - // Vary by this list of parameters, read from the net/http.Request FormValue method. - Params []string - - // Vary by this list of cookie names, read from the net/http.Request Cookie method. - Cookies []string - - // Use this separator string to concatenate the various criteria of the VaryBy struct. - // Defaults to a newline character if empty (\n). - Separator string - - // 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 -} - -// Key returns the key for this request based on the criteria defined by the VaryBy struct. -func (vb *VaryBy) Key(r *http.Request) string { - var buf bytes.Buffer - - if vb == nil { - return "" // Special case for no vary-by option - } - if vb.Custom != nil { - // A custom key generator is specified - return vb.Custom(r) - } - sep := vb.Separator - if sep == "" { - sep = "\n" // Separator defaults to newline - } - if vb.RemoteAddr { - buf.WriteString(strings.ToLower(r.RemoteAddr) + sep) - } - if vb.Method { - buf.WriteString(strings.ToLower(r.Method) + sep) - } - for _, h := range vb.Headers { - buf.WriteString(strings.ToLower(r.Header.Get(h)) + sep) - } - if vb.Path { - buf.WriteString(r.URL.Path + sep) - } - for _, p := range vb.Params { - buf.WriteString(r.FormValue(p) + sep) - } - for _, c := range vb.Cookies { - ck, err := r.Cookie(c) - if err == nil { - buf.WriteString(ck.Value) - } - buf.WriteString(sep) // Write the separator anyway, whether or not the cookie exists - } - return buf.String() -} diff --git a/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go b/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go deleted file mode 100644 index 91b7ae0ae..000000000 --- a/vendor/gopkg.in/throttled/throttled.v1/varyby_test.go +++ /dev/null @@ -1,56 +0,0 @@ -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)) - } - } -} 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.v2/LICENSE b/vendor/gopkg.in/throttled/throttled.v2/LICENSE new file mode 100644 index 000000000..f9616483e --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/LICENSE @@ -0,0 +1,12 @@ +Copyright (c) 2014, Martin Angers and Contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 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 +# 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:/throttled.git + +# Create a branch, make your changes, test them and commit. +git checkout -b my-new-feature +# +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.v2/varyby.go b/vendor/gopkg.in/throttled/throttled.v2/varyby.go new file mode 100644 index 000000000..dc6a01718 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/varyby.go @@ -0,0 +1,78 @@ +package throttled + +import ( + "bytes" + "net/http" + "strings" +) + +// VaryBy defines the criteria to use to group requests. +type VaryBy struct { + // Vary by the RemoteAddr as specified by the net/http.Request field. + RemoteAddr bool + + // Vary by the HTTP Method as specified by the net/http.Request field. + Method bool + + // Vary by the URL's Path as specified by the Path field of the net/http.Request + // URL field. + Path bool + + // Vary by this list of header names, read from the net/http.Request Header field. + Headers []string + + // Vary by this list of parameters, read from the net/http.Request FormValue method. + Params []string + + // Vary by this list of cookie names, read from the net/http.Request Cookie method. + Cookies []string + + // Use this separator string to concatenate the various criteria of the VaryBy struct. + // Defaults to a newline character if empty (\n). + Separator string + + // 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 +} + +// Key returns the key for this request based on the criteria defined by the VaryBy struct. +func (vb *VaryBy) Key(r *http.Request) string { + var buf bytes.Buffer + + if vb == nil { + return "" // Special case for no vary-by option + } + if vb.Custom != nil { + // A custom key generator is specified + return vb.Custom(r) + } + sep := vb.Separator + if sep == "" { + sep = "\n" // Separator defaults to newline + } + if vb.RemoteAddr { + buf.WriteString(strings.ToLower(r.RemoteAddr) + sep) + } + if vb.Method { + buf.WriteString(strings.ToLower(r.Method) + sep) + } + for _, h := range vb.Headers { + buf.WriteString(strings.ToLower(r.Header.Get(h)) + sep) + } + if vb.Path { + buf.WriteString(r.URL.Path + sep) + } + for _, p := range vb.Params { + buf.WriteString(r.FormValue(p) + sep) + } + for _, c := range vb.Cookies { + ck, err := r.Cookie(c) + if err == nil { + buf.WriteString(ck.Value) + } + buf.WriteString(sep) // Write the separator anyway, whether or not the cookie exists + } + return buf.String() +} diff --git a/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go new file mode 100644 index 000000000..66a5f4e98 --- /dev/null +++ b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go @@ -0,0 +1,58 @@ +package throttled_test + +import ( + "net/http" + "net/url" + "testing" + + "gopkg.in/throttled/throttled.v2" +) + +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 *throttled.VaryBy + r *http.Request + k string + }{ + 0: {nil, &http.Request{}, ""}, + 1: {&throttled.VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"}, + 2: { + &throttled.VaryBy{Method: true, Path: true}, + &http.Request{Method: "POST", URL: u}, + "post\n/test/path\n", + }, + 3: { + &throttled.VaryBy{Headers: []string{"Content-length"}}, + &http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}}, + "123\n", + }, + 4: { + &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: { + &throttled.VaryBy{Cookies: []string{"ssn"}}, + &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}}, + "test\n", + }, + 6: { + &throttled.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)) + } + } +} diff --git a/vendor/gopkg.in/yaml.v2/LICENSE b/vendor/gopkg.in/yaml.v2/LICENSE index a68e67f01..866d74a7a 100644 --- a/vendor/gopkg.in/yaml.v2/LICENSE +++ b/vendor/gopkg.in/yaml.v2/LICENSE @@ -1,188 +1,13 @@ +Copyright 2011-2016 Canonical Ltd. -Copyright (c) 2011-2014 - Canonical Inc. +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at -This software is licensed under the LGPLv3, included below. + http://www.apache.org/licenses/LICENSE-2.0 -As a special exception to the GNU Lesser General Public License version 3 -("LGPL3"), the copyright holders of this Library give you permission to -convey to a third party a Combined Work that links statically or dynamically -to this Library without providing any Minimal Corresponding Source or -Minimal Application Code as set out in 4d or providing the installation -information set out in section 4e, provided that you comply with the other -provisions of LGPL3 and provided that you meet, for the Application the -terms and conditions of the license(s) which apply to the Application. - -Except as stated in this special exception, the provisions of LGPL3 will -continue to comply in full to this Library. If you modify this Library, you -may apply this exception to your version of this Library, but you are not -obliged to do so. If you do not wish to do so, delete this exception -statement from your version. This exception does not (and cannot) modify any -license terms which apply to the Application, with which you must still -comply. - - - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/vendor/gopkg.in/yaml.v2/README.md b/vendor/gopkg.in/yaml.v2/README.md index 7b8bd8670..1884de6a7 100644 --- a/vendor/gopkg.in/yaml.v2/README.md +++ b/vendor/gopkg.in/yaml.v2/README.md @@ -42,7 +42,7 @@ The package API for yaml v2 will remain stable as described in [gopkg.in](https: License ------- -The yaml package is licensed under the LGPL with an exception that allows it to be linked statically. Please see the LICENSE file for details. +The yaml package is licensed under the Apache License 2.0. Please see the LICENSE file for details. Example -- cgit v1.2.3-1-g7c22