You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@servicecomb.apache.org by GitBox <gi...@apache.org> on 2017/12/29 09:51:39 UTC

[GitHub] little-cui closed pull request #235: SCB-143 Add new rate limiter

little-cui closed pull request #235: SCB-143 Add new rate limiter
URL: https://github.com/apache/incubator-servicecomb-service-center/pull/235
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/pkg/httplimiter/httpratelimiter.go b/pkg/httplimiter/httpratelimiter.go
new file mode 100644
index 0000000..9128d5b
--- /dev/null
+++ b/pkg/httplimiter/httpratelimiter.go
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+package httplimiter
+
+import (
+	"net/http"
+	"strconv"
+	"strings"
+	"time"
+	"github.com/apache/incubator-servicecomb-service-center/pkg/ratelimiter"
+	"fmt"
+	"sync"
+)
+
+type HTTPErrorMessage struct {
+	Message    string
+	StatusCode int
+}
+
+func (httpErrorMessage *HTTPErrorMessage) Error() string {
+	return fmt.Sprintf("%v: %v", httpErrorMessage.StatusCode, httpErrorMessage.Message)
+}
+
+
+type HttpLimiter struct {
+	HttpMessage    string
+	ContentType    string
+	StatusCode     int
+	RequestLimit   int64
+	TTL            time.Duration
+	IPLookups      []string
+	Methods        []string
+	Headers        map[string][]string
+	BasicAuthUsers []string
+	leakyBuckets   map[string]*ratelimiter.LeakyBucket
+	sync.RWMutex
+}
+
+
+
+func LimitBySegments(limiter *HttpLimiter, keys []string) *HTTPErrorMessage {
+	if limiter.LimitExceeded(strings.Join(keys, "|")) {
+		return &HTTPErrorMessage{Message: limiter.HttpMessage, StatusCode: limiter.StatusCode}
+	}
+
+	return nil
+}
+
+func LimitByRequest(httpLimiter *HttpLimiter, r *http.Request) *HTTPErrorMessage {
+	sliceKeys := BuildSegments(httpLimiter, r)
+
+	for _, keys := range sliceKeys {
+		httpError := LimitBySegments(httpLimiter, keys)
+		if httpError != nil {
+			return httpError
+		}
+	}
+
+	return nil
+}
+
+func BuildSegments(httpLimiter *HttpLimiter, r *http.Request) [][]string {
+	remoteIP := getRemoteIP(httpLimiter.IPLookups, r)
+	urlPath := r.URL.Path
+	sliceKeys := make([][]string, 0)
+
+	if remoteIP == "" {
+		return sliceKeys
+	}
+
+	if httpLimiter.Methods != nil && httpLimiter.Headers != nil && httpLimiter.BasicAuthUsers != nil {
+		if checkExistence(httpLimiter.Methods, r.Method) {
+			for headerKey, headerValues := range httpLimiter.Headers {
+				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
+					username, _, ok := r.BasicAuth()
+					if ok && checkExistence(httpLimiter.BasicAuthUsers, username) {
+						sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method, headerKey, username})
+					}
+
+				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
+					for _, headerValue := range headerValues {
+						username, _, ok := r.BasicAuth()
+						if ok && checkExistence(httpLimiter.BasicAuthUsers, username) {
+							sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method, headerKey, headerValue, username})
+						}
+					}
+				}
+			}
+		}
+
+	} else if httpLimiter.Methods != nil && httpLimiter.Headers != nil {
+		if checkExistence(httpLimiter.Methods, r.Method) {
+			for headerKey, headerValues := range httpLimiter.Headers {
+				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
+					sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method, headerKey})
+
+				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
+					for _, headerValue := range headerValues {
+						sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method, headerKey, headerValue})
+					}
+				}
+			}
+		}
+
+	} else if httpLimiter.Methods != nil && httpLimiter.BasicAuthUsers != nil {
+		if checkExistence(httpLimiter.Methods, r.Method) {
+			username, _, ok := r.BasicAuth()
+			if ok && checkExistence(httpLimiter.BasicAuthUsers, username) {
+				sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method, username})
+			}
+		}
+
+	} else if httpLimiter.Methods != nil {
+		if checkExistence(httpLimiter.Methods, r.Method) {
+			sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, r.Method})
+		}
+
+	} else if httpLimiter.Headers != nil {
+		for headerKey, headerValues := range httpLimiter.Headers {
+			if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
+				sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, headerKey})
+
+			} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
+				for _, headerValue := range headerValues {
+					sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, headerKey, headerValue})
+				}
+			}
+		}
+
+	} else if httpLimiter.BasicAuthUsers != nil {
+		username, _, ok := r.BasicAuth()
+		if ok && checkExistence(httpLimiter.BasicAuthUsers, username) {
+			sliceKeys = append(sliceKeys, []string{remoteIP, urlPath, username})
+		}
+	} else {
+		sliceKeys = append(sliceKeys, []string{remoteIP, urlPath})
+	}
+
+	return sliceKeys
+}
+
+func SetResponseHeaders(limiter *HttpLimiter, w http.ResponseWriter) {
+	w.Header().Add("X-Rate-Limit-Limit", strconv.FormatInt(limiter.RequestLimit, 10))
+	w.Header().Add("X-Rate-Limit-Duration", limiter.TTL.String())
+}
+
+func checkExistence(sliceString []string, needle string) bool {
+	for _, b := range sliceString {
+		if b == needle {
+			return true
+		}
+	}
+	return false
+}
+
+func ipAddrFromRemoteAddr(s string) string {
+	idx := strings.LastIndex(s, ":")
+	if idx == -1 {
+		return s
+	}
+	return s[:idx]
+}
+
+func getRemoteIP(ipLookups []string, r *http.Request) string {
+	realIP := r.Header.Get("X-Real-IP")
+	forwardedFor := r.Header.Get("X-Forwarded-For")
+
+	for _, lookup := range ipLookups {
+		if lookup == "RemoteAddr" {
+			return ipAddrFromRemoteAddr(r.RemoteAddr)
+		}
+		if lookup == "X-Forwarded-For" && forwardedFor != "" {
+			parts := strings.Split(forwardedFor, ",")
+			for i, p := range parts {
+				parts[i] = strings.TrimSpace(p)
+			}
+			return parts[0]
+		}
+		if lookup == "X-Real-IP" && realIP != "" {
+			return realIP
+		}
+	}
+
+	return ""
+}
+
+
+func NewHttpLimiter(max int64, ttl time.Duration) *HttpLimiter {
+	limiter := &HttpLimiter{RequestLimit: max, TTL: ttl}
+	limiter.ContentType = "text/plain; charset=utf-8"
+	limiter.HttpMessage = "You have reached maximum request limit."
+	limiter.StatusCode = http.StatusTooManyRequests
+	limiter.leakyBuckets = make(map[string]*ratelimiter.LeakyBucket)
+	limiter.IPLookups = []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
+
+	return limiter
+}
+
+
+func (rateLimiter *HttpLimiter) LimitExceeded(key string) bool {
+	rateLimiter.Lock()
+	if _, found := rateLimiter.leakyBuckets[key]; !found {
+		rateLimiter.leakyBuckets[key] = ratelimiter.NewLeakyBucket(rateLimiter.TTL, rateLimiter.RequestLimit, rateLimiter.RequestLimit)
+	}
+	_, isInLimits := rateLimiter.leakyBuckets[key].MaximumTakeDuration(1, 0)
+	rateLimiter.Unlock()
+	if isInLimits {
+		return false
+	}
+	return true
+}
+
+
diff --git a/pkg/ratelimiter/ratelimiter.go b/pkg/ratelimiter/ratelimiter.go
new file mode 100644
index 0000000..8184108
--- /dev/null
+++ b/pkg/ratelimiter/ratelimiter.go
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * 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.
+ */
+
+package ratelimiter
+
+import (
+	"time"
+	"sync"
+)
+
+type LeakyBucket struct {
+	startTime       time.Time
+	capacity        int64
+	quantum         int64
+	interval        time.Duration
+	mutex           sync.Mutex
+	available       int64
+	availableTicker int64
+}
+
+
+func NewLeakyBucket(fillInterval time.Duration, capacity, quantum int64) *LeakyBucket {
+	if fillInterval <= 0 {
+		panic("leaky bucket fill interval is not > 0")
+	}
+	if capacity <= 0 {
+		panic("leaky bucket capacity is not > 0")
+	}
+	if quantum <= 0 {
+		panic("leaky bucket quantum is not > 0")
+	}
+	return &LeakyBucket{
+		startTime: time.Now(),
+		capacity:  capacity,
+		quantum:   quantum,
+		available: capacity,
+		interval:  fillInterval,
+	}
+}
+
+func (leakyBucket *LeakyBucket) Wait(count int64) {
+	if d := leakyBucket.Take(count); d > 0 {
+		time.Sleep(d)
+	}
+}
+
+func (leakyBucket *LeakyBucket) MaximumWaitDuration(count int64, maxWait time.Duration) bool {
+	d, ok := leakyBucket.MaximumTakeDuration(count, maxWait)
+	if d > 0 {
+		time.Sleep(d)
+	}
+	return ok
+}
+
+const sleepForever time.Duration = 0x7fffffffffffffff
+
+func (leakyBucket *LeakyBucket) Take(count int64) time.Duration {
+	d, _ := leakyBucket.take(time.Now(), count, sleepForever)
+	return d
+}
+
+func (leakyBucket *LeakyBucket) MaximumTakeDuration(count int64, maxWait time.Duration) (time.Duration, bool) {
+	return leakyBucket.take(time.Now(), count, maxWait)
+}
+
+
+func (leakyBucket *LeakyBucket) Rate() float64 {
+	return 1e9 * float64(leakyBucket.quantum) / float64(leakyBucket.interval)
+}
+
+func (leakyBucket *LeakyBucket) take(now time.Time, count int64, maxWait time.Duration) (time.Duration, bool) {
+	if count <= 0 {
+		return 0, true
+	}
+	leakyBucket.mutex.Lock()
+	defer leakyBucket.mutex.Unlock()
+
+	currentTick := leakyBucket.adjust(now)
+	avail := leakyBucket.available - count
+	if avail >= 0 {
+		leakyBucket.available = avail
+		return 0, true
+	}
+	endTick := currentTick + (-avail+leakyBucket.quantum-1)/leakyBucket.quantum
+	endTime := leakyBucket.startTime.Add(time.Duration(endTick) * leakyBucket.interval)
+	waitTime := endTime.Sub(now)
+	if waitTime > maxWait {
+		return 0, false
+	}
+	leakyBucket.available = avail
+	return waitTime, true
+}
+
+func (leakyBucket *LeakyBucket) adjust(now time.Time) (currentTick int64) {
+	currentTick = int64(now.Sub(leakyBucket.startTime) / leakyBucket.interval)
+
+	if leakyBucket.available >= leakyBucket.capacity {
+		return
+	}
+	leakyBucket.available += (currentTick - leakyBucket.availableTicker) * leakyBucket.quantum
+	if leakyBucket.available > leakyBucket.capacity {
+		leakyBucket.available = leakyBucket.capacity
+	}
+	leakyBucket.availableTicker = currentTick
+	return
+}
+
diff --git a/server/interceptor/ratelimiter/limiter.go b/server/interceptor/ratelimiter/limiter.go
index 6476942..5845b78 100644
--- a/server/interceptor/ratelimiter/limiter.go
+++ b/server/interceptor/ratelimiter/limiter.go
@@ -18,10 +18,9 @@ package ratelimiter
 
 import (
 	"errors"
+	"github.com/apache/incubator-servicecomb-service-center/pkg/httplimiter"
 	"github.com/apache/incubator-servicecomb-service-center/pkg/util"
 	"github.com/apache/incubator-servicecomb-service-center/server/core"
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
 	"net/http"
 	"strings"
 	"sync"
@@ -31,7 +30,7 @@ import (
 type Limiter struct {
 	conns int64
 
-	tbLimiter *config.Limiter
+	httpLimiter *httplimiter.HttpLimiter
 }
 
 var limiter *Limiter
@@ -61,9 +60,9 @@ func (this *Limiter) LoadConfig() {
 		ttl = time.Hour
 	}
 	this.conns = core.ServerInfo.Config.LimitConnections
-	this.tbLimiter = tollbooth.NewLimiter(this.conns, ttl)
+	this.httpLimiter = httplimiter.NewHttpLimiter(this.conns, ttl)
 	iplookups := core.ServerInfo.Config.LimitIPLookup
-	this.tbLimiter.IPLookups = strings.Split(iplookups, ",")
+	this.httpLimiter.IPLookups = strings.Split(iplookups, ",")
 
 	util.Logger().Warnf(nil, "Rate-limit Load config, ttl: %s, conns: %d, iplookups: %s", ttl, this.conns, iplookups)
 }
@@ -73,13 +72,13 @@ func (this *Limiter) Handle(w http.ResponseWriter, r *http.Request) error {
 		return nil
 	}
 
-	tollbooth.SetResponseHeaders(this.tbLimiter, w)
-	httpError := tollbooth.LimitByRequest(this.tbLimiter, r)
+	httplimiter.SetResponseHeaders(this.httpLimiter, w)
+	httpError := httplimiter.LimitByRequest(this.httpLimiter, r)
 	if httpError != nil {
-		w.Header().Add("Content-Type", this.tbLimiter.MessageContentType)
+		w.Header().Add("Content-Type", this.httpLimiter.ContentType)
 		w.WriteHeader(httpError.StatusCode)
 		w.Write(util.StringToBytesWithNoCopy(httpError.Message))
-		util.Logger().Warn("Reached maximum request limit!", nil)
+		util.Logger().Warnf(nil, "Reached maximum request limit for %s host and %s url", r.RemoteAddr, r.RequestURI)
 		return errors.New(httpError.Message)
 	}
 	return nil
diff --git a/server/interceptor/ratelimiter/limiter_test.go b/server/interceptor/ratelimiter/limiter_test.go
index f382d01..eb8a752 100644
--- a/server/interceptor/ratelimiter/limiter_test.go
+++ b/server/interceptor/ratelimiter/limiter_test.go
@@ -25,7 +25,7 @@ import (
 	"time"
 )
 
-var _ = Describe("Limiter", func() {
+var _ = Describe("HttpLimiter", func() {
 	var (
 		limiter *Limiter
 	)
@@ -39,7 +39,7 @@ var _ = Describe("Limiter", func() {
 			It("should be ok", func() {
 				Expect(limiter.conns).To(Equal(int64(0)))
 				res := []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
-				for i, val := range limiter.tbLimiter.IPLookups {
+				for i, val := range limiter.httpLimiter.IPLookups {
 					Expect(val).To(Equal(res[i]))
 				}
 			})
@@ -57,7 +57,7 @@ var _ = Describe("Limiter", func() {
 		Context("Connections > 0", func() {
 			It("should not be router", func() {
 				limiter.conns = 1
-				limiter.tbLimiter = tollbooth.NewLimiter(1, time.Second)
+				limiter.httpLimiter = tollbooth.NewLimiter(1, time.Second)
 				resp, err := http.Get(ts.URL)
 				Expect(err).To(BeNil())
 				Expect(resp.StatusCode).To(Equal(http.StatusOK))
@@ -70,7 +70,7 @@ var _ = Describe("Limiter", func() {
 		Context("Connections <= 0", func() {
 			It("should be router", func() {
 				limiter.conns = 0
-				limiter.tbLimiter = tollbooth.NewLimiter(0, time.Second)
+				limiter.httpLimiter = tollbooth.NewLimiter(0, time.Second)
 				resp, err := http.Get(ts.URL)
 				Expect(err).To(BeNil())
 				Expect(resp.StatusCode).To(Equal(http.StatusOK))
diff --git a/server/interceptor/ratelimiter/ratelimiter_suite_test.go b/server/interceptor/ratelimiter/ratelimiter_suite_test.go
index 8360af3..af2baf8 100644
--- a/server/interceptor/ratelimiter/ratelimiter_suite_test.go
+++ b/server/interceptor/ratelimiter/ratelimiter_suite_test.go
@@ -25,5 +25,5 @@ import (
 
 func TestNet(t *testing.T) {
 	RegisterFailHandler(Fail)
-	RunSpecs(t, "RateLimiter Suite")
+	RunSpecs(t, "HttpLimiter Suite")
 }
diff --git a/vendor/github.com/didip/tollbooth/.gitignore b/vendor/github.com/didip/tollbooth/.gitignore
deleted file mode 100644
index 1043f53..0000000
--- a/vendor/github.com/didip/tollbooth/.gitignore
+++ /dev/null
@@ -1,2 +0,0 @@
-/debug
-/.vscode
diff --git a/vendor/github.com/didip/tollbooth/LICENSE b/vendor/github.com/didip/tollbooth/LICENSE
deleted file mode 100644
index 349ee1c..0000000
--- a/vendor/github.com/didip/tollbooth/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2015 Didip Kerabat
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/vendor/github.com/didip/tollbooth/README.md b/vendor/github.com/didip/tollbooth/README.md
deleted file mode 100644
index 08dd9a2..0000000
--- a/vendor/github.com/didip/tollbooth/README.md
+++ /dev/null
@@ -1,65 +0,0 @@
-[![GoDoc](https://godoc.org/github.com/didip/tollbooth?status.svg)](http://godoc.org/github.com/didip/tollbooth)
-[![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/didip/tollbooth/master/LICENSE)
-
-## Tollbooth
-
-This is a generic middleware to rate-limit HTTP requests.
-
-**NOTE:** This library is considered finished, any new activities are probably centered around `thirdparty` modules.
-
-
-## Five Minutes Tutorial
-```
-package main
-
-import (
-    "github.com/didip/tollbooth"
-    "net/http"
-    "time"
-)
-
-func HelloHandler(w http.ResponseWriter, req *http.Request) {
-    w.Write([]byte("Hello, World!"))
-}
-
-func main() {
-    // Create a request limiter per handler.
-    http.Handle("/", tollbooth.LimitFuncHandler(tollbooth.NewLimiter(1, time.Second), HelloHandler))
-    http.ListenAndServe(":12345", nil)
-}
-```
-
-## Features
-
-1. Rate-limit by request's remote IP, path, methods, custom headers, & basic auth usernames.
-    ```
-    limiter := tollbooth.NewLimiter(1, time.Second)
-
-    // Configure list of places to look for IP address.
-    // By default it's: "RemoteAddr", "X-Forwarded-For", "X-Real-IP"
-    // If your application is behind a proxy, set "X-Forwarded-For" first.
-    limiter.IPLookups = []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
-
-    // Limit only GET and POST requests.
-    limiter.Methods = []string{"GET", "POST"}
-
-    // Limit request headers containing certain values.
-    // Typically, you prefetched these values from the database.
-    limiter.Headers = make(map[string][]string)
-    limiter.Headers["X-Access-Token"] = []string{"abc123", "xyz098"}
-
-    // Limit based on basic auth usernames.
-    // Typically, you prefetched these values from the database.
-    limiter.BasicAuthUsers = []string{"bob", "joe", "didip"}
-    ```
-
-2. Each request handler can be rate-limited individually.
-
-3. Compose your own middleware by using `LimitByKeys()`.
-
-4. Tollbooth does not require external storage since it uses an algorithm called [Token Bucket](http://en.wikipedia.org/wiki/Token_bucket) [(Go library: ratelimit)](https://github.com/juju/ratelimit).
-
-
-# Other Web Frameworks
-
-Support for other web frameworks are defined under `/thirdparty` directory.
diff --git a/vendor/github.com/didip/tollbooth/config/config.go b/vendor/github.com/didip/tollbooth/config/config.go
deleted file mode 100644
index c66e5bb..0000000
--- a/vendor/github.com/didip/tollbooth/config/config.go
+++ /dev/null
@@ -1,77 +0,0 @@
-// Package config provides data structure to configure rate-limiter.
-package config
-
-import (
-	"sync"
-	"time"
-
-	"github.com/juju/ratelimit"
-)
-
-// NewLimiter is a constructor for Limiter.
-func NewLimiter(max int64, ttl time.Duration) *Limiter {
-	limiter := &Limiter{Max: max, TTL: ttl}
-	limiter.MessageContentType = "text/plain; charset=utf-8"
-	limiter.Message = "You have reached maximum request limit."
-	limiter.StatusCode = 429
-	limiter.tokenBuckets = make(map[string]*ratelimit.Bucket)
-	limiter.IPLookups = []string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}
-
-	return limiter
-}
-
-// Limiter is a config struct to limit a particular request handler.
-type Limiter struct {
-	// HTTP message when limit is reached.
-	Message string
-
-	// Content-Type for Message
-	MessageContentType string
-
-	// HTTP status code when limit is reached.
-	StatusCode int
-
-	// Maximum number of requests to limit per duration.
-	Max int64
-
-	// Duration of rate-limiter.
-	TTL time.Duration
-
-	// List of places to look up IP address.
-	// Default is "RemoteAddr", "X-Forwarded-For", "X-Real-IP".
-	// You can rearrange the order as you like.
-	IPLookups []string
-
-	// List of HTTP Methods to limit (GET, POST, PUT, etc.).
-	// Empty means limit all methods.
-	Methods []string
-
-	// List of HTTP headers to limit.
-	// Empty means skip headers checking.
-	Headers map[string][]string
-
-	// List of basic auth usernames to limit.
-	BasicAuthUsers []string
-
-	// Throttler struct
-	tokenBuckets map[string]*ratelimit.Bucket
-
-	sync.RWMutex
-}
-
-// LimitReached returns a bool indicating if the Bucket identified by key ran out of tokens.
-func (l *Limiter) LimitReached(key string) bool {
-	l.Lock()
-	if _, found := l.tokenBuckets[key]; !found {
-		l.tokenBuckets[key] = ratelimit.NewBucketWithQuantum(l.TTL, l.Max, l.Max)
-	}
-
-	_, isSoonerThanMaxWait := l.tokenBuckets[key].TakeMaxDuration(1, 0)
-	l.Unlock()
-
-	if isSoonerThanMaxWait {
-		return false
-	}
-
-	return true
-}
diff --git a/vendor/github.com/didip/tollbooth/config/config_benchmark_test.go b/vendor/github.com/didip/tollbooth/config/config_benchmark_test.go
deleted file mode 100644
index 77ae567..0000000
--- a/vendor/github.com/didip/tollbooth/config/config_benchmark_test.go
+++ /dev/null
@@ -1,15 +0,0 @@
-package config
-
-import (
-	"testing"
-	"time"
-)
-
-func BenchmarkLimitReached(b *testing.B) {
-	limiter := NewLimiter(1, time.Second)
-	key := "127.0.0.1|/"
-
-	for i := 0; i < b.N; i++ {
-		limiter.LimitReached(key)
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/config/config_test.go b/vendor/github.com/didip/tollbooth/config/config_test.go
deleted file mode 100644
index 7b3b3ab..0000000
--- a/vendor/github.com/didip/tollbooth/config/config_test.go
+++ /dev/null
@@ -1,57 +0,0 @@
-package config
-
-import (
-	"testing"
-	"time"
-)
-
-func TestConstructor(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	if limiter.Max != 1 {
-		t.Errorf("Max field is incorrect. Value: %v", limiter.Max)
-	}
-	if limiter.TTL != time.Second {
-		t.Errorf("TTL field is incorrect. Value: %v", limiter.TTL)
-	}
-	if limiter.Message != "You have reached maximum request limit." {
-		t.Errorf("Message field is incorrect. Value: %v", limiter.Message)
-	}
-	if limiter.StatusCode != 429 {
-		t.Errorf("StatusCode field is incorrect. Value: %v", limiter.StatusCode)
-	}
-}
-
-func TestLimitReached(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	key := "127.0.0.1|/"
-
-	if limiter.LimitReached(key) == true {
-		t.Error("First time count should not reached the limit.")
-	}
-
-	if limiter.LimitReached(key) == false {
-		t.Error("Second time count should return true because it exceeds 1 request per second.")
-	}
-
-	<-time.After(1 * time.Second)
-	if limiter.LimitReached(key) == true {
-		t.Error("Third time count should not reached the limit because the 1 second window has passed.")
-	}
-}
-
-func TestMuchHigherMaxRequests(t *testing.T) {
-	numRequests := 1000
-	limiter := NewLimiter(int64(numRequests), time.Second)
-	key := "127.0.0.1|/"
-
-	for i := 0; i < numRequests; i++ {
-		if limiter.LimitReached(key) == true {
-			t.Errorf("N(%v) limit should not be reached.", i)
-		}
-	}
-
-	if limiter.LimitReached(key) == false {
-		t.Errorf("N(%v) limit should be reached because it exceeds %v request per second.", numRequests+2, numRequests)
-	}
-
-}
diff --git a/vendor/github.com/didip/tollbooth/errors/errors.go b/vendor/github.com/didip/tollbooth/errors/errors.go
deleted file mode 100644
index 149bc5a..0000000
--- a/vendor/github.com/didip/tollbooth/errors/errors.go
+++ /dev/null
@@ -1,15 +0,0 @@
-// Package errors provide data structure for errors.
-package errors
-
-import "fmt"
-
-// HTTPError is an error struct that returns both message and status code.
-type HTTPError struct {
-	Message    string
-	StatusCode int
-}
-
-// Error returns error message.
-func (httperror *HTTPError) Error() string {
-	return fmt.Sprintf("%v: %v", httperror.StatusCode, httperror.Message)
-}
diff --git a/vendor/github.com/didip/tollbooth/errors/errors_test.go b/vendor/github.com/didip/tollbooth/errors/errors_test.go
deleted file mode 100644
index a5d04ae..0000000
--- a/vendor/github.com/didip/tollbooth/errors/errors_test.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package errors
-
-import "testing"
-
-func TestError(t *testing.T) {
-	errs := HTTPError{"blah", 429}
-	if errs.Error() == "" {
-		t.Errorf("Unable to print Error(). Value: %v", errs.Error())
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/libstring/libstring.go b/vendor/github.com/didip/tollbooth/libstring/libstring.go
deleted file mode 100644
index 8e6a356..0000000
--- a/vendor/github.com/didip/tollbooth/libstring/libstring.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Package libstring provides various string related functions.
-package libstring
-
-import (
-	"net/http"
-	"strings"
-)
-
-// StringInSlice finds needle in a slice of strings.
-func StringInSlice(sliceString []string, needle string) bool {
-	for _, b := range sliceString {
-		if b == needle {
-			return true
-		}
-	}
-	return false
-}
-
-func ipAddrFromRemoteAddr(s string) string {
-	idx := strings.LastIndex(s, ":")
-	if idx == -1 {
-		return s
-	}
-	return s[:idx]
-}
-
-// RemoteIP finds IP Address given http.Request struct.
-func RemoteIP(ipLookups []string, r *http.Request) string {
-	realIP := r.Header.Get("X-Real-IP")
-	forwardedFor := r.Header.Get("X-Forwarded-For")
-
-	for _, lookup := range ipLookups {
-		if lookup == "RemoteAddr" {
-			return ipAddrFromRemoteAddr(r.RemoteAddr)
-		}
-		if lookup == "X-Forwarded-For" && forwardedFor != "" {
-			// X-Forwarded-For is potentially a list of addresses separated with ","
-			parts := strings.Split(forwardedFor, ",")
-			for i, p := range parts {
-				parts[i] = strings.TrimSpace(p)
-			}
-			return parts[0]
-		}
-		if lookup == "X-Real-IP" && realIP != "" {
-			return realIP
-		}
-	}
-
-	return ""
-}
diff --git a/vendor/github.com/didip/tollbooth/libstring/libstring_test.go b/vendor/github.com/didip/tollbooth/libstring/libstring_test.go
deleted file mode 100644
index f11245a..0000000
--- a/vendor/github.com/didip/tollbooth/libstring/libstring_test.go
+++ /dev/null
@@ -1,81 +0,0 @@
-package libstring
-
-import (
-	"net/http"
-	"strings"
-	"testing"
-)
-
-func TestStringInSlice(t *testing.T) {
-	if StringInSlice([]string{"alice", "dan", "didip", "jason", "karl"}, "brotato") {
-		t.Error("brotato should not be in slice.")
-	}
-}
-
-func TestIPAddrFromRemoteAddr(t *testing.T) {
-	if ipAddrFromRemoteAddr("127.0.0.1:8989") != "127.0.0.1" {
-		t.Errorf("ipAddrFromRemoteAddr did not chop the port number correctly.")
-	}
-}
-
-func TestRemoteIPDefault(t *testing.T) {
-	ipLookups := []string{"RemoteAddr", "X-Real-IP"}
-	ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", ipv6)
-
-	ip := RemoteIP(ipLookups, request)
-	if ip != request.RemoteAddr {
-		t.Errorf("Did not get the right IP. IP: %v", ip)
-	}
-	if ip == ipv6 {
-		t.Errorf("X-Real-IP should have been skipped. IP: %v", ip)
-	}
-}
-
-func TestRemoteIPForwardedFor(t *testing.T) {
-	ipLookups := []string{"X-Forwarded-For", "X-Real-IP", "RemoteAddr"}
-	ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Forwarded-For", "54.223.11.104")
-	request.Header.Set("X-Real-IP", ipv6)
-
-	ip := RemoteIP(ipLookups, request)
-	if ip != "54.223.11.104" {
-		t.Errorf("Did not get the right IP. IP: %v", ip)
-	}
-	if ip == ipv6 {
-		t.Errorf("X-Real-IP should have been skipped. IP: %v", ip)
-	}
-}
-
-func TestRemoteIPRealIP(t *testing.T) {
-	ipLookups := []string{"X-Real-IP", "X-Forwarded-For", "RemoteAddr"}
-	ipv6 := "2601:7:1c82:4097:59a0:a80b:2841:b8c8"
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Forwarded-For", "54.223.11.104")
-	request.Header.Set("X-Real-IP", ipv6)
-
-	ip := RemoteIP(ipLookups, request)
-	if ip != ipv6 {
-		t.Errorf("Did not get the right IP. IP: %v", ip)
-	}
-	if ip == "54.223.11.104" {
-		t.Errorf("X-Forwarded-For should have been skipped. IP: %v", ip)
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/README.md b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/README.md
deleted file mode 100644
index cb99aae..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/README.md
+++ /dev/null
@@ -1,33 +0,0 @@
-## tollbooth_echo
-
-[Echo](https://github.com/webx-top/echo) middleware for rate limiting HTTP requests.
-
-
-## Five Minutes Tutorial
-
-```
-package main
-
-import (
-	"time"
-
-	"github.com/didip/tollbooth/thirdparty/tollbooth_echo"
-	"github.com/didip/tollbooth"
-	"github.com/webx-top/echo"
-	"github.com/webx-top/echo/engine/standard"
-)
-
-func main() {
-	e := echo.New()
-
-	// Create a limiter struct.
-	limiter := tollbooth.NewLimiter(1, time.Second)
-
-	e.Get("/", echo.HandlerFunc(func(c echo.Context) error {
-		return c.String(200, "Hello, World!")
-	}), tollbooth_echo.LimitHandler(limiter))
-
-	e.Run(standard.New(":4444"))
-}
-
-```
\ No newline at end of file
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/test/main.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/test/main.go
deleted file mode 100644
index ec179ad..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/test/main.go
+++ /dev/null
@@ -1,23 +0,0 @@
-package main
-
-import (
-	"time"
-
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/thirdparty/tollbooth_echo"
-	"github.com/webx-top/echo"
-	"github.com/webx-top/echo/engine/standard"
-)
-
-func main() {
-	e := echo.New()
-
-	// Create a limiter struct.
-	limiter := tollbooth.NewLimiter(1, time.Second)
-
-	e.Get("/", echo.HandlerFunc(func(c echo.Context) error {
-		return c.String(200, "Hello, World!")
-	}), tollbooth_echo.LimitHandler(limiter))
-
-	e.Run(standard.New(":4444"))
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/tollbooth_echo.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/tollbooth_echo.go
deleted file mode 100644
index 50f466b..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_echo/tollbooth_echo.go
+++ /dev/null
@@ -1,182 +0,0 @@
-package tollbooth_echo
-
-import (
-	"strings"
-
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
-	"github.com/didip/tollbooth/errors"
-	"github.com/didip/tollbooth/libstring"
-	"github.com/webx-top/echo"
-	"github.com/webx-top/echo/engine"
-)
-
-func LimitMiddleware(limiter *config.Limiter) echo.MiddlewareFunc {
-	return func(h echo.Handler) echo.Handler {
-		return echo.HandlerFunc(func(c echo.Context) error {
-			httpError := LimitByRequest(limiter, c.Request())
-			if httpError != nil {
-				return c.String(httpError.StatusCode, httpError.Message)
-			}
-			return h.Handle(c)
-		})
-	}
-}
-
-func LimitHandler(limiter *config.Limiter) echo.MiddlewareFunc {
-	return LimitMiddleware(limiter)
-}
-
-// LimitByRequest builds keys based on http.Request struct,
-// loops through all the keys, and check if any one of them returns HTTPError.
-func LimitByRequest(limiter *config.Limiter, r engine.Request) *errors.HTTPError {
-	sliceKeys := BuildKeys(limiter, r)
-
-	// Loop sliceKeys and check if one of them has error.
-	for _, keys := range sliceKeys {
-		httpError := tollbooth.LimitByKeys(limiter, keys)
-		if httpError != nil {
-			return httpError
-		}
-	}
-
-	return nil
-}
-
-// StringInSlice finds needle in a slice of strings.
-func StringInSlice(sliceString []string, needle string) bool {
-	for _, b := range sliceString {
-		if b == needle {
-			return true
-		}
-	}
-	return false
-}
-
-func ipAddrFromRemoteAddr(s string) string {
-	idx := strings.LastIndex(s, ":")
-	if idx == -1 {
-		return s
-	}
-	return s[:idx]
-}
-
-// RemoteIP finds IP Address given http.Request struct.
-func RemoteIP(ipLookups []string, r engine.Request) string {
-	realIP := r.Header().Get("X-Real-IP")
-	forwardedFor := r.Header().Get("X-Forwarded-For")
-
-	for _, lookup := range ipLookups {
-		if lookup == "RemoteAddr" {
-			return ipAddrFromRemoteAddr(r.RemoteAddress())
-		}
-		if lookup == "X-Forwarded-For" && forwardedFor != "" {
-			// X-Forwarded-For is potentially a list of addresses separated with ","
-			parts := strings.Split(forwardedFor, ",")
-			for i, p := range parts {
-				parts[i] = strings.TrimSpace(p)
-			}
-			return parts[0]
-		}
-		if lookup == "X-Real-IP" && realIP != "" {
-			return realIP
-		}
-	}
-
-	return ""
-}
-
-// BuildKeys generates a slice of keys to rate-limit by given config and request structs.
-func BuildKeys(limiter *config.Limiter, r engine.Request) [][]string {
-	remoteIP := RemoteIP(limiter.IPLookups, r)
-	path := r.URL().Path()
-	sliceKeys := make([][]string, 0)
-
-	// Don't BuildKeys if remoteIP is blank.
-	if remoteIP == "" {
-		return sliceKeys
-	}
-
-	if limiter.Methods != nil && limiter.Headers != nil && limiter.BasicAuthUsers != nil {
-		// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
-		if StringInSlice(limiter.Methods, r.Method()) {
-			for headerKey, headerValues := range limiter.Headers {
-				if (headerValues == nil || len(headerValues) <= 0) && r.Header().Get(headerKey) != "" {
-					// If header values are empty, rate-limit all request with headerKey.
-					username, _, ok := r.BasicAuth()
-					if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method(), headerKey, username})
-					}
-
-				} else if len(headerValues) > 0 && r.Header().Get(headerKey) != "" {
-					// If header values are not empty, rate-limit all request with headerKey and headerValues.
-					for _, headerValue := range headerValues {
-						username, _, ok := r.BasicAuth()
-						if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-							sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method(), headerKey, headerValue, username})
-						}
-					}
-				}
-			}
-		}
-
-	} else if limiter.Methods != nil && limiter.Headers != nil {
-		// Limit by HTTP methods and HTTP headers+values.
-		if libstring.StringInSlice(limiter.Methods, r.Method()) {
-			for headerKey, headerValues := range limiter.Headers {
-				if (headerValues == nil || len(headerValues) <= 0) && r.Header().Get(headerKey) != "" {
-					// If header values are empty, rate-limit all request with headerKey.
-					sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method(), headerKey})
-
-				} else if len(headerValues) > 0 && r.Header().Get(headerKey) != "" {
-					// If header values are not empty, rate-limit all request with headerKey and headerValues.
-					for _, headerValue := range headerValues {
-						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method(), headerKey, headerValue})
-					}
-				}
-			}
-		}
-
-	} else if limiter.Methods != nil && limiter.BasicAuthUsers != nil {
-		// Limit by HTTP methods and Basic Auth credentials.
-		if libstring.StringInSlice(limiter.Methods, r.Method()) {
-			username, _, ok := r.BasicAuth()
-			if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-				sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method(), username})
-			}
-		}
-
-	} else if limiter.Methods != nil {
-		// Limit by HTTP methods.
-		if libstring.StringInSlice(limiter.Methods, r.Method()) {
-			sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method()})
-		}
-
-	} else if limiter.Headers != nil {
-		// Limit by HTTP headers+values.
-		for headerKey, headerValues := range limiter.Headers {
-			if (headerValues == nil || len(headerValues) <= 0) && r.Header().Get(headerKey) != "" {
-				// If header values are empty, rate-limit all request with headerKey.
-				sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey})
-
-			} else if len(headerValues) > 0 && r.Header().Get(headerKey) != "" {
-				// If header values are not empty, rate-limit all request with headerKey and headerValues.
-				for _, headerValue := range headerValues {
-					sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue})
-				}
-			}
-		}
-
-	} else if limiter.BasicAuthUsers != nil {
-		// Limit by Basic Auth credentials.
-		username, _, ok := r.BasicAuth()
-		if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-			sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
-		}
-	} else {
-		// Default: Limit by remoteIP and path.
-		sliceKeys = append(sliceKeys, []string{remoteIP, path})
-	}
-
-	return sliceKeys
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/README.md b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/README.md
deleted file mode 100644
index a1a82db..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
-## tollbooth_gin
-
-[Gin](https://github.com/gin-gonic) middleware for rate limiting HTTP requests.
-
-
-## Five Minutes Tutorial
-
-```
-package main
-
-import (
-    "github.com/didip/tollbooth"
-    "github.com/didip/tollbooth/thirdparty/tollbooth_gin"
-    "github.com/gin-gonic/gin"
-    "time"
-)
-
-func main() {
-    r := gin.New()
-
-    // Create a limiter struct.
-    limiter := tollbooth.NewLimiter(1, time.Second)
-
-    r.GET("/", tollbooth_gin.LimitHandler(limiter), func(c *gin.Context) {
-        c.String(200, "Hello, world!")
-    })
-
-    r.Run(":12345")
-}
-
-```
\ No newline at end of file
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/tollbooth_gin.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/tollbooth_gin.go
deleted file mode 100644
index cbd15ce..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gin/tollbooth_gin.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package tollbooth_gin
-
-import (
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
-	"github.com/gin-gonic/gin"
-)
-
-func LimitHandler(limiter *config.Limiter) gin.HandlerFunc {
-	return func(c *gin.Context) {
-		httpError := tollbooth.LimitByRequest(limiter, c.Request)
-		if httpError != nil {
-			c.String(httpError.StatusCode, httpError.Message)
-			c.Abort()
-		} else {
-			c.Next()
-		}
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/README.md b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/README.md
deleted file mode 100644
index b96c654..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/README.md
+++ /dev/null
@@ -1,36 +0,0 @@
-## tollbooth_gorestful
-
-Middleware for [go-restful](https://github.com/emicklei/go-restful)
-
-Import package `thirdparty/tollbooth_gorestful` and use `tollbooth_gorestful.LimitHandler` to which your own handler can be passed
-
-## Five Minutes Tutorial
-
-```
-package resources
-
-import (
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/thirdparty/tollbooth_gorestful"
-	"github.com/emicklei/go-restful"
-)
-
-type User struct {
-	ID     string  `json:"id"`
-	Email  string  `json:"email"`
-}
-
-func (u *User) Register(container *restful.Container) {
-	ws := new(restful.WebService)
-	ws.Path("/users").Doc("Manage Users").Consumes(restful.MIME_JSON).Produces(restful.MIME_JSON)
-
-	ws.Route(ws.GET("/{id}").To(tollbooth_gorestful.LimitHandler(u.GetUser, tollbooth.NewLimiter(3, time.Minute))).
-		// docs
-		Doc("get a user").
-		Operation("GetUser").
-		Param(ws.PathParameter("id", "identifier of the user").DataType("string")).
-		Writes(User{}))
-
-	container.Add(ws)
-}
-```
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/tollbooth_gorestful.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/tollbooth_gorestful.go
deleted file mode 100644
index d0303f1..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_gorestful/tollbooth_gorestful.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package tollbooth_gorestful
-
-import (
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
-	"github.com/emicklei/go-restful"
-)
-
-func LimitHandler(handler restful.RouteFunction, limiter *config.Limiter) restful.RouteFunction {
-	return func(request *restful.Request, response *restful.Response) {
-		httpError := tollbooth.LimitByRequest(limiter, request.Request)
-		if httpError != nil {
-			response.WriteErrorString(429, "429: Too Many Requests")
-			return
-		}
-
-		handler(request, response)
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/README.md b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/README.md
deleted file mode 100644
index c991baa..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/README.md
+++ /dev/null
@@ -1,44 +0,0 @@
-## tollbooth_httprouter
-
-[httprouter](https://github.com/julienschmidt/httprouter) middleware for rate limiting HTTP requests.
-
-
-## Five Minutes Tutorial
-
-```
-package main
-
-import (
-    "time"
-    "log"
-
-    "github.com/didip/tollbooth"
-    "github.com/didip/tollbooth/thirdparty/tollbooth_httprouter"
-    "github.com/julienschmidt/httprouter"
-)
-
-Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
-    fmt.Fprint(w, "Welcome!\n")
-}
-
-func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
-    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
-}
-
-func main() {
-    router := httprouter.New()
-
-    // Create a limiter struct.
-    limiter := tollbooth.NewLimiter(1, time.Second)
-
-    // Index route without limiting.
-    router.GET("/", Index)
-
-    // Hello route with limiting.
-    router.GET("/hello/:name",
-        tollbooth_httprouter.LimitHandler(Hello, limiter),
-    )
-
-    log.Fatal(http.ListenAndServe(":8080", router))
-}
-```
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/tollbooth_httprouter.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/tollbooth_httprouter.go
deleted file mode 100644
index fb6db78..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_httprouter/tollbooth_httprouter.go
+++ /dev/null
@@ -1,22 +0,0 @@
-package tollbooth_httprouter
-
-import (
-	"net/http"
-
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
-	"github.com/julienschmidt/httprouter"
-)
-
-// RateLimit is a rate limiting middleware
-func LimitHandler(handler httprouter.Handle, limiter *config.Limiter) httprouter.Handle {
-	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
-		httpError := tollbooth.LimitByRequest(limiter, r)
-		if httpError != nil {
-			http.Error(w, httpError.Message, httpError.StatusCode)
-			return
-		}
-
-		handler(w, r, ps)
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/README.md b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/README.md
deleted file mode 100644
index c950fd5..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/README.md
+++ /dev/null
@@ -1,42 +0,0 @@
-## tollbooth_negroni
-
-[Negroni](https://github.com/urfave/negroni) middleware for rate limiting HTTP requests.
-
-
-## Five Minutes Tutorial
-
-```
-package main
-
-import (
-    "github.com/urfave/negroni"
-    "github.com/didip/tollbooth"
-    "github.com/didip/tollbooth/thirdparty/tollbooth_negroni"
-    "net/http"
-    "time"
-)
-
-func HelloHandler() http.Handler {
-    handleFunc := func(w http.ResponseWriter, r *http.Request) {
-        w.Write([]byte("Hello, world!"))
-    }
-
-    return http.HandlerFunc(handleFunc)
-}
-
-func main() {
-    // Create a limiter struct.
-    limiter := tollbooth.NewLimiter(1, time.Second)
-
-    mux := http.NewServeMux()
-
-    mux.Handle("/", negroni.New(
-        tollbooth_negroni.LimitHandler(limiter),
-        negroni.Wrap(HelloHandler()),
-    ))
-
-    n := negroni.Classic()
-    n.UseHandler(mux)
-    n.Run(":12345")
-}
-```
\ No newline at end of file
diff --git a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/tollbooth_negroni.go b/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/tollbooth_negroni.go
deleted file mode 100644
index 9f12b02..0000000
--- a/vendor/github.com/didip/tollbooth/thirdparty/tollbooth_negroni/tollbooth_negroni.go
+++ /dev/null
@@ -1,27 +0,0 @@
-package tollbooth_negroni
-
-import (
-	"github.com/didip/tollbooth"
-	"github.com/didip/tollbooth/config"
-	"github.com/urfave/negroni"
-	"net/http"
-)
-
-func LimitHandler(limiter *config.Limiter) negroni.HandlerFunc {
-	return negroni.HandlerFunc(func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
-		httpError := tollbooth.LimitByRequest(limiter, r)
-		if httpError != nil {
-			w.Header().Add("Content-Type", limiter.MessageContentType)
-			/* RHMOD Fix for error "http: multiple response.WriteHeader calls"
-			   Reverse the sequence of the functions calls w.WriteHeader() and w.Write()
-			*/
-			w.WriteHeader(httpError.StatusCode)
-			w.Write([]byte(httpError.Message))
-			return
-
-		} else {
-			next(w, r)
-		}
-
-	})
-}
diff --git a/vendor/github.com/didip/tollbooth/tollbooth.go b/vendor/github.com/didip/tollbooth/tollbooth.go
deleted file mode 100644
index c6b1fe3..0000000
--- a/vendor/github.com/didip/tollbooth/tollbooth.go
+++ /dev/null
@@ -1,170 +0,0 @@
-// Package tollbooth provides rate-limiting logic to HTTP request handler.
-package tollbooth
-
-import (
-	"net/http"
-	"strconv"
-	"strings"
-	"time"
-
-	"github.com/didip/tollbooth/config"
-	"github.com/didip/tollbooth/errors"
-	"github.com/didip/tollbooth/libstring"
-)
-
-// NewLimiter is a convenience function to config.NewLimiter.
-func NewLimiter(max int64, ttl time.Duration) *config.Limiter {
-	return config.NewLimiter(max, ttl)
-}
-
-// LimitByKeys keeps track number of request made by keys separated by pipe.
-// It returns HTTPError when limit is exceeded.
-func LimitByKeys(limiter *config.Limiter, keys []string) *errors.HTTPError {
-	if limiter.LimitReached(strings.Join(keys, "|")) {
-		return &errors.HTTPError{Message: limiter.Message, StatusCode: limiter.StatusCode}
-	}
-
-	return nil
-}
-
-// LimitByRequest builds keys based on http.Request struct,
-// loops through all the keys, and check if any one of them returns HTTPError.
-func LimitByRequest(limiter *config.Limiter, r *http.Request) *errors.HTTPError {
-	sliceKeys := BuildKeys(limiter, r)
-
-	// Loop sliceKeys and check if one of them has error.
-	for _, keys := range sliceKeys {
-		httpError := LimitByKeys(limiter, keys)
-		if httpError != nil {
-			return httpError
-		}
-	}
-
-	return nil
-}
-
-// BuildKeys generates a slice of keys to rate-limit by given config and request structs.
-func BuildKeys(limiter *config.Limiter, r *http.Request) [][]string {
-	remoteIP := libstring.RemoteIP(limiter.IPLookups, r)
-	path := r.URL.Path
-	sliceKeys := make([][]string, 0)
-
-	// Don't BuildKeys if remoteIP is blank.
-	if remoteIP == "" {
-		return sliceKeys
-	}
-
-	if limiter.Methods != nil && limiter.Headers != nil && limiter.BasicAuthUsers != nil {
-		// Limit by HTTP methods and HTTP headers+values and Basic Auth credentials.
-		if libstring.StringInSlice(limiter.Methods, r.Method) {
-			for headerKey, headerValues := range limiter.Headers {
-				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
-					// If header values are empty, rate-limit all request with headerKey.
-					username, _, ok := r.BasicAuth()
-					if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, username})
-					}
-
-				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
-					// If header values are not empty, rate-limit all request with headerKey and headerValues.
-					for _, headerValue := range headerValues {
-						username, _, ok := r.BasicAuth()
-						if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-							sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue, username})
-						}
-					}
-				}
-			}
-		}
-
-	} else if limiter.Methods != nil && limiter.Headers != nil {
-		// Limit by HTTP methods and HTTP headers+values.
-		if libstring.StringInSlice(limiter.Methods, r.Method) {
-			for headerKey, headerValues := range limiter.Headers {
-				if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
-					// If header values are empty, rate-limit all request with headerKey.
-					sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey})
-
-				} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
-					// If header values are not empty, rate-limit all request with headerKey and headerValues.
-					for _, headerValue := range headerValues {
-						sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, headerValue})
-					}
-				}
-			}
-		}
-
-	} else if limiter.Methods != nil && limiter.BasicAuthUsers != nil {
-		// Limit by HTTP methods and Basic Auth credentials.
-		if libstring.StringInSlice(limiter.Methods, r.Method) {
-			username, _, ok := r.BasicAuth()
-			if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-				sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, username})
-			}
-		}
-
-	} else if limiter.Methods != nil {
-		// Limit by HTTP methods.
-		if libstring.StringInSlice(limiter.Methods, r.Method) {
-			sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method})
-		}
-
-	} else if limiter.Headers != nil {
-		// Limit by HTTP headers+values.
-		for headerKey, headerValues := range limiter.Headers {
-			if (headerValues == nil || len(headerValues) <= 0) && r.Header.Get(headerKey) != "" {
-				// If header values are empty, rate-limit all request with headerKey.
-				sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey})
-
-			} else if len(headerValues) > 0 && r.Header.Get(headerKey) != "" {
-				// If header values are not empty, rate-limit all request with headerKey and headerValues.
-				for _, headerValue := range headerValues {
-					sliceKeys = append(sliceKeys, []string{remoteIP, path, headerKey, headerValue})
-				}
-			}
-		}
-
-	} else if limiter.BasicAuthUsers != nil {
-		// Limit by Basic Auth credentials.
-		username, _, ok := r.BasicAuth()
-		if ok && libstring.StringInSlice(limiter.BasicAuthUsers, username) {
-			sliceKeys = append(sliceKeys, []string{remoteIP, path, username})
-		}
-	} else {
-		// Default: Limit by remoteIP and path.
-		sliceKeys = append(sliceKeys, []string{remoteIP, path})
-	}
-
-	return sliceKeys
-}
-
-// SetResponseHeaders configures X-Rate-Limit-Limit and X-Rate-Limit-Duration
-func SetResponseHeaders(limiter *config.Limiter, w http.ResponseWriter) {
-	w.Header().Add("X-Rate-Limit-Limit", strconv.FormatInt(limiter.Max, 10))
-	w.Header().Add("X-Rate-Limit-Duration", limiter.TTL.String())
-}
-
-// LimitHandler is a middleware that performs rate-limiting given http.Handler struct.
-func LimitHandler(limiter *config.Limiter, next http.Handler) http.Handler {
-	middle := func(w http.ResponseWriter, r *http.Request) {
-		SetResponseHeaders(limiter, w)
-
-		httpError := LimitByRequest(limiter, r)
-		if httpError != nil {
-			w.Header().Add("Content-Type", limiter.MessageContentType)
-			w.WriteHeader(httpError.StatusCode)
-			w.Write([]byte(httpError.Message))
-			return
-		}
-
-		// There's no rate-limit error, serve the next handler.
-		next.ServeHTTP(w, r)
-	}
-
-	return http.HandlerFunc(middle)
-}
-
-// LimitFuncHandler is a middleware that performs rate-limiting given request handler function.
-func LimitFuncHandler(limiter *config.Limiter, nextFunc func(http.ResponseWriter, *http.Request)) http.Handler {
-	return LimitHandler(limiter, http.HandlerFunc(nextFunc))
-}
diff --git a/vendor/github.com/didip/tollbooth/tollbooth_benchmark_test.go b/vendor/github.com/didip/tollbooth/tollbooth_benchmark_test.go
deleted file mode 100644
index 9136ec3..0000000
--- a/vendor/github.com/didip/tollbooth/tollbooth_benchmark_test.go
+++ /dev/null
@@ -1,34 +0,0 @@
-package tollbooth
-
-import (
-	"fmt"
-	"net/http"
-	"strings"
-	"testing"
-	"time"
-)
-
-func BenchmarkLimitByKeys(b *testing.B) {
-	limiter := NewLimiter(1, time.Second) // Only 1 request per second is allowed.
-
-	for i := 0; i < b.N; i++ {
-		LimitByKeys(limiter, []string{"127.0.0.1", "/"})
-	}
-}
-
-func BenchmarkBuildKeys(b *testing.B) {
-	limiter := NewLimiter(1, time.Second)
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		fmt.Printf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-	for i := 0; i < b.N; i++ {
-		sliceKeys := BuildKeys(limiter, request)
-		if len(sliceKeys) == 0 {
-			fmt.Print("Length of sliceKeys should never be empty.")
-		}
-	}
-}
diff --git a/vendor/github.com/didip/tollbooth/tollbooth_test.go b/vendor/github.com/didip/tollbooth/tollbooth_test.go
deleted file mode 100644
index c7792d1..0000000
--- a/vendor/github.com/didip/tollbooth/tollbooth_test.go
+++ /dev/null
@@ -1,266 +0,0 @@
-package tollbooth
-
-import (
-	"net/http"
-	"strings"
-	"testing"
-	"time"
-)
-
-func TestLimitByKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second) // Only 1 request per second is allowed.
-
-	httperror := LimitByKeys(limiter, []string{"127.0.0.1", "/"})
-	if httperror != nil {
-		t.Errorf("First time count should not return error. Error: %v", httperror.Error())
-	}
-
-	httperror = LimitByKeys(limiter, []string{"127.0.0.1", "/"})
-	if httperror == nil {
-		t.Errorf("Second time count should return error because it exceeds 1 request per second.")
-	}
-
-	<-time.After(1 * time.Second)
-	httperror = LimitByKeys(limiter, []string{"127.0.0.1", "/"})
-	if httperror != nil {
-		t.Errorf("Third time count should not return error because the 1 second window has passed.")
-	}
-}
-
-func TestDefaultBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.IPLookups = []string{"X-Forwarded-For", "X-Real-IP", "RemoteAddr"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-
-	sliceKeys := BuildKeys(limiter, request)
-	if len(sliceKeys) == 0 {
-		t.Error("Length of sliceKeys should never be empty.")
-	}
-
-	for _, keys := range sliceKeys {
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The first chunk should be remote IP. KeyChunk: %v", keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The second chunk should be request path. KeyChunk: %v", keyChunk)
-			}
-		}
-	}
-}
-
-func TestBasicAuthBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.BasicAuthUsers = []string{"bro"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-
-	request.SetBasicAuth("bro", "tato")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 3 {
-			t.Error("Keys should be made of 3 parts.")
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "bro" {
-				t.Errorf("The (%v) chunk should be request username. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-}
-
-func TestCustomHeadersBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.Headers = make(map[string][]string)
-	limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-	request.Header.Set("X-Auth-Token", "totally-top-secret")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 4 {
-			t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "X-Auth-Token" {
-				t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 3 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-}
-
-func TestRequestMethodBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.Methods = []string{"GET"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 3 {
-			t.Errorf("Keys should be made of 3 parts. Keys: %v", keys)
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "GET" {
-				t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-}
-
-func TestRequestMethodAndCustomHeadersBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.Methods = []string{"GET"}
-	limiter.Headers = make(map[string][]string)
-	limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-	request.Header.Set("X-Auth-Token", "totally-top-secret")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 5 {
-			t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "GET" {
-				t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 3 && keyChunk != "X-Auth-Token" {
-				t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 4 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-}
-
-func TestRequestMethodAndBasicAuthUsersBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.Methods = []string{"GET"}
-	limiter.BasicAuthUsers = []string{"bro"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-	request.SetBasicAuth("bro", "tato")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 4 {
-			t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "GET" {
-				t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 3 && keyChunk != "bro" {
-				t.Errorf("The (%v) chunk should be basic auth user. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-}
-
-func TestRequestMethodCustomHeadersAndBasicAuthUsersBuildKeys(t *testing.T) {
-	limiter := NewLimiter(1, time.Second)
-	limiter.Methods = []string{"GET"}
-	limiter.Headers = make(map[string][]string)
-	limiter.Headers["X-Auth-Token"] = []string{"totally-top-secret", "another-secret"}
-	limiter.BasicAuthUsers = []string{"bro"}
-
-	request, err := http.NewRequest("GET", "/", strings.NewReader("Hello, world!"))
-	if err != nil {
-		t.Errorf("Unable to create new HTTP request. Error: %v", err)
-	}
-
-	request.Header.Set("X-Real-IP", "2601:7:1c82:4097:59a0:a80b:2841:b8c8")
-	request.Header.Set("X-Auth-Token", "totally-top-secret")
-	request.SetBasicAuth("bro", "tato")
-
-	for _, keys := range BuildKeys(limiter, request) {
-		if len(keys) != 6 {
-			t.Errorf("Keys should be made of 4 parts. Keys: %v", keys)
-		}
-		for i, keyChunk := range keys {
-			if i == 0 && keyChunk != request.Header.Get("X-Real-IP") {
-				t.Errorf("The (%v) chunk should be remote IP. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 1 && keyChunk != request.URL.Path {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 2 && keyChunk != "GET" {
-				t.Errorf("The (%v) chunk should be request method. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 3 && keyChunk != "X-Auth-Token" {
-				t.Errorf("The (%v) chunk should be request header. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 4 && (keyChunk != "totally-top-secret" && keyChunk != "another-secret") {
-				t.Errorf("The (%v) chunk should be request path. KeyChunk: %v", i+1, keyChunk)
-			}
-			if i == 5 && keyChunk != "bro" {
-				t.Errorf("The (%v) chunk should be basic auth user. KeyChunk: %v", i+1, keyChunk)
-			}
-		}
-	}
-
-}
diff --git a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/LICENSE b/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/LICENSE
deleted file mode 100644
index ade9307..0000000
--- a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/LICENSE
+++ /dev/null
@@ -1,191 +0,0 @@
-All files in this repository are licensed as follows. If you contribute
-to this repository, it is assumed that you license your contribution
-under the same license unless you state otherwise.
-
-All files Copyright (C) 2015 Canonical Ltd. unless otherwise specified in the file.
-
-This software is licensed under the LGPLv3, included below.
-
-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. <http://fsf.org/>
- 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.
diff --git a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/README.md b/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/README.md
deleted file mode 100644
index a0fdfe2..0000000
--- a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/README.md
+++ /dev/null
@@ -1,117 +0,0 @@
-# ratelimit
---
-    import "github.com/juju/ratelimit"
-
-The ratelimit package provides an efficient token bucket implementation. See
-http://en.wikipedia.org/wiki/Token_bucket.
-
-## Usage
-
-#### func  Reader
-
-```go
-func Reader(r io.Reader, bucket *Bucket) io.Reader
-```
-Reader returns a reader that is rate limited by the given token bucket. Each
-token in the bucket represents one byte.
-
-#### func  Writer
-
-```go
-func Writer(w io.Writer, bucket *Bucket) io.Writer
-```
-Writer returns a writer that is rate limited by the given token bucket. Each
-token in the bucket represents one byte.
-
-#### type Bucket
-
-```go
-type Bucket struct {
-}
-```
-
-Bucket represents a token bucket that fills at a predetermined rate. Methods on
-Bucket may be called concurrently.
-
-#### func  NewBucket
-
-```go
-func NewBucket(fillInterval time.Duration, capacity int64) *Bucket
-```
-NewBucket returns a new token bucket that fills at the rate of one token every
-fillInterval, up to the given maximum capacity. Both arguments must be positive.
-The bucket is initially full.
-
-#### func  NewBucketWithQuantum
-
-```go
-func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket
-```
-NewBucketWithQuantum is similar to NewBucket, but allows the specification of
-the quantum size - quantum tokens are added every fillInterval.
-
-#### func  NewBucketWithRate
-
-```go
-func NewBucketWithRate(rate float64, capacity int64) *Bucket
-```
-NewBucketWithRate returns a token bucket that fills the bucket at the rate of
-rate tokens per second up to the given maximum capacity. Because of limited
-clock resolution, at high rates, the actual rate may be up to 1% different from
-the specified rate.
-
-#### func (*Bucket) Rate
-
-```go
-func (tb *Bucket) Rate() float64
-```
-Rate returns the fill rate of the bucket, in tokens per second.
-
-#### func (*Bucket) Take
-
-```go
-func (tb *Bucket) Take(count int64) time.Duration
-```
-Take takes count tokens from the bucket without blocking. It returns the time
-that the caller should wait until the tokens are actually available.
-
-Note that if the request is irrevocable - there is no way to return tokens to
-the bucket once this method commits us to taking them.
-
-#### func (*Bucket) TakeAvailable
-
-```go
-func (tb *Bucket) TakeAvailable(count int64) int64
-```
-TakeAvailable takes up to count immediately available tokens from the bucket. It
-returns the number of tokens removed, or zero if there are no available tokens.
-It does not block.
-
-#### func (*Bucket) TakeMaxDuration
-
-```go
-func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool)
-```
-TakeMaxDuration is like Take, except that it will only take tokens from the
-bucket if the wait time for the tokens is no greater than maxWait.
-
-If it would take longer than maxWait for the tokens to become available, it does
-nothing and reports false, otherwise it returns the time that the caller should
-wait until the tokens are actually available, and reports true.
-
-#### func (*Bucket) Wait
-
-```go
-func (tb *Bucket) Wait(count int64)
-```
-Wait takes count tokens from the bucket, waiting until they are available.
-
-#### func (*Bucket) WaitMaxDuration
-
-```go
-func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool
-```
-WaitMaxDuration is like Wait except that it will only take tokens from the
-bucket if it needs to wait for no greater than maxWait. It reports whether any
-tokens have been removed from the bucket If no tokens have been removed, it
-returns immediately.
diff --git a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/ratelimit.go b/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/ratelimit.go
deleted file mode 100644
index 3ef32fb..0000000
--- a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/ratelimit.go
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright 2014 Canonical Ltd.
-// Licensed under the LGPLv3 with static-linking exception.
-// See LICENCE file for details.
-
-// The ratelimit package provides an efficient token bucket implementation
-// that can be used to limit the rate of arbitrary things.
-// See http://en.wikipedia.org/wiki/Token_bucket.
-package ratelimit
-
-import (
-	"math"
-	"strconv"
-	"sync"
-	"time"
-)
-
-// Bucket represents a token bucket that fills at a predetermined rate.
-// Methods on Bucket may be called concurrently.
-type Bucket struct {
-	startTime    time.Time
-	capacity     int64
-	quantum      int64
-	fillInterval time.Duration
-
-	// The mutex guards the fields following it.
-	mu sync.Mutex
-
-	// avail holds the number of available tokens
-	// in the bucket, as of availTick ticks from startTime.
-	// It will be negative when there are consumers
-	// waiting for tokens.
-	avail     int64
-	availTick int64
-}
-
-// NewBucket returns a new token bucket that fills at the
-// rate of one token every fillInterval, up to the given
-// maximum capacity. Both arguments must be
-// positive. The bucket is initially full.
-func NewBucket(fillInterval time.Duration, capacity int64) *Bucket {
-	return NewBucketWithQuantum(fillInterval, capacity, 1)
-}
-
-// rateMargin specifes the allowed variance of actual
-// rate from specified rate. 1% seems reasonable.
-const rateMargin = 0.01
-
-// NewBucketWithRate returns a token bucket that fills the bucket
-// at the rate of rate tokens per second up to the given
-// maximum capacity. Because of limited clock resolution,
-// at high rates, the actual rate may be up to 1% different from the
-// specified rate.
-func NewBucketWithRate(rate float64, capacity int64) *Bucket {
-	for quantum := int64(1); quantum < 1<<50; quantum = nextQuantum(quantum) {
-		fillInterval := time.Duration(1e9 * float64(quantum) / rate)
-		if fillInterval <= 0 {
-			continue
-		}
-		tb := NewBucketWithQuantum(fillInterval, capacity, quantum)
-		if diff := math.Abs(tb.Rate() - rate); diff/rate <= rateMargin {
-			return tb
-		}
-	}
-	panic("cannot find suitable quantum for " + strconv.FormatFloat(rate, 'g', -1, 64))
-}
-
-// nextQuantum returns the next quantum to try after q.
-// We grow the quantum exponentially, but slowly, so we
-// get a good fit in the lower numbers.
-func nextQuantum(q int64) int64 {
-	q1 := q * 11 / 10
-	if q1 == q {
-		q1++
-	}
-	return q1
-}
-
-// NewBucketWithQuantum is similar to NewBucket, but allows
-// the specification of the quantum size - quantum tokens
-// are added every fillInterval.
-func NewBucketWithQuantum(fillInterval time.Duration, capacity, quantum int64) *Bucket {
-	if fillInterval <= 0 {
-		panic("token bucket fill interval is not > 0")
-	}
-	if capacity <= 0 {
-		panic("token bucket capacity is not > 0")
-	}
-	if quantum <= 0 {
-		panic("token bucket quantum is not > 0")
-	}
-	return &Bucket{
-		startTime:    time.Now(),
-		capacity:     capacity,
-		quantum:      quantum,
-		avail:        capacity,
-		fillInterval: fillInterval,
-	}
-}
-
-// Wait takes count tokens from the bucket, waiting until they are
-// available.
-func (tb *Bucket) Wait(count int64) {
-	if d := tb.Take(count); d > 0 {
-		time.Sleep(d)
-	}
-}
-
-// WaitMaxDuration is like Wait except that it will
-// only take tokens from the bucket if it needs to wait
-// for no greater than maxWait. It reports whether
-// any tokens have been removed from the bucket
-// If no tokens have been removed, it returns immediately.
-func (tb *Bucket) WaitMaxDuration(count int64, maxWait time.Duration) bool {
-	d, ok := tb.TakeMaxDuration(count, maxWait)
-	if d > 0 {
-		time.Sleep(d)
-	}
-	return ok
-}
-
-const infinityDuration time.Duration = 0x7fffffffffffffff
-
-// Take takes count tokens from the bucket without blocking. It returns
-// the time that the caller should wait until the tokens are actually
-// available.
-//
-// Note that if the request is irrevocable - there is no way to return
-// tokens to the bucket once this method commits us to taking them.
-func (tb *Bucket) Take(count int64) time.Duration {
-	d, _ := tb.take(time.Now(), count, infinityDuration)
-	return d
-}
-
-// TakeMaxDuration is like Take, except that
-// it will only take tokens from the bucket if the wait
-// time for the tokens is no greater than maxWait.
-//
-// If it would take longer than maxWait for the tokens
-// to become available, it does nothing and reports false,
-// otherwise it returns the time that the caller should
-// wait until the tokens are actually available, and reports
-// true.
-func (tb *Bucket) TakeMaxDuration(count int64, maxWait time.Duration) (time.Duration, bool) {
-	return tb.take(time.Now(), count, maxWait)
-}
-
-// TakeAvailable takes up to count immediately available tokens from the
-// bucket. It returns the number of tokens removed, or zero if there are
-// no available tokens. It does not block.
-func (tb *Bucket) TakeAvailable(count int64) int64 {
-	return tb.takeAvailable(time.Now(), count)
-}
-
-// takeAvailable is the internal version of TakeAvailable - it takes the
-// current time as an argument to enable easy testing.
-func (tb *Bucket) takeAvailable(now time.Time, count int64) int64 {
-	if count <= 0 {
-		return 0
-	}
-	tb.mu.Lock()
-	defer tb.mu.Unlock()
-
-	tb.adjust(now)
-	if tb.avail <= 0 {
-		return 0
-	}
-	if count > tb.avail {
-		count = tb.avail
-	}
-	tb.avail -= count
-	return count
-}
-
-// Available returns the number of available tokens. It will be negative
-// when there are consumers waiting for tokens. Note that if this
-// returns greater than zero, it does not guarantee that calls that take
-// tokens from the buffer will succeed, as the number of available
-// tokens could have changed in the meantime. This method is intended
-// primarily for metrics reporting and debugging.
-func (tb *Bucket) Available() int64 {
-	return tb.available(time.Now())
-}
-
-// available is the internal version of available - it takes the current time as
-// an argument to enable easy testing.
-func (tb *Bucket) available(now time.Time) int64 {
-	tb.mu.Lock()
-	defer tb.mu.Unlock()
-	tb.adjust(now)
-	return tb.avail
-}
-
-// Capacity returns the capacity that the bucket was created with.
-func (tb *Bucket) Capacity() int64 {
-	return tb.capacity
-}
-
-// Rate returns the fill rate of the bucket, in tokens per second.
-func (tb *Bucket) Rate() float64 {
-	return 1e9 * float64(tb.quantum) / float64(tb.fillInterval)
-}
-
-// take is the internal version of Take - it takes the current time as
-// an argument to enable easy testing.
-func (tb *Bucket) take(now time.Time, count int64, maxWait time.Duration) (time.Duration, bool) {
-	if count <= 0 {
-		return 0, true
-	}
-	tb.mu.Lock()
-	defer tb.mu.Unlock()
-
-	currentTick := tb.adjust(now)
-	avail := tb.avail - count
-	if avail >= 0 {
-		tb.avail = avail
-		return 0, true
-	}
-	// Round up the missing tokens to the nearest multiple
-	// of quantum - the tokens won't be available until
-	// that tick.
-	endTick := currentTick + (-avail+tb.quantum-1)/tb.quantum
-	endTime := tb.startTime.Add(time.Duration(endTick) * tb.fillInterval)
-	waitTime := endTime.Sub(now)
-	if waitTime > maxWait {
-		return 0, false
-	}
-	tb.avail = avail
-	return waitTime, true
-}
-
-// adjust adjusts the current bucket capacity based on the current time.
-// It returns the current tick.
-func (tb *Bucket) adjust(now time.Time) (currentTick int64) {
-	currentTick = int64(now.Sub(tb.startTime) / tb.fillInterval)
-
-	if tb.avail >= tb.capacity {
-		return
-	}
-	tb.avail += (currentTick - tb.availTick) * tb.quantum
-	if tb.avail > tb.capacity {
-		tb.avail = tb.capacity
-	}
-	tb.availTick = currentTick
-	return
-}
diff --git a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/reader.go b/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/reader.go
deleted file mode 100644
index 6403bf7..0000000
--- a/vendor/github.com/didip/tollbooth/vendor/github.com/juju/ratelimit/reader.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2014 Canonical Ltd.
-// Licensed under the LGPLv3 with static-linking exception.
-// See LICENCE file for details.
-
-package ratelimit
-
-import "io"
-
-type reader struct {
-	r      io.Reader
-	bucket *Bucket
-}
-
-// Reader returns a reader that is rate limited by
-// the given token bucket. Each token in the bucket
-// represents one byte.
-func Reader(r io.Reader, bucket *Bucket) io.Reader {
-	return &reader{
-		r:      r,
-		bucket: bucket,
-	}
-}
-
-func (r *reader) Read(buf []byte) (int, error) {
-	n, err := r.r.Read(buf)
-	if n <= 0 {
-		return n, err
-	}
-	r.bucket.Wait(int64(n))
-	return n, err
-}
-
-type writer struct {
-	w      io.Writer
-	bucket *Bucket
-}
-
-// Writer returns a reader that is rate limited by
-// the given token bucket. Each token in the bucket
-// represents one byte.
-func Writer(w io.Writer, bucket *Bucket) io.Writer {
-	return &writer{
-		w:      w,
-		bucket: bucket,
-	}
-}
-
-func (w *writer) Write(buf []byte) (int, error) {
-	w.bucket.Wait(int64(len(buf)))
-	return w.w.Write(buf)
-}
diff --git a/vendor/github.com/didip/tollbooth/vendor/vendor.json b/vendor/github.com/didip/tollbooth/vendor/vendor.json
deleted file mode 100644
index e4dbff8..0000000
--- a/vendor/github.com/didip/tollbooth/vendor/vendor.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
-	"comment": "",
-	"ignore": "test",
-	"package": [
-		{
-			"checksumSHA1": "sKheT5xw89Tbu2Q071FQO27CVmE=",
-			"path": "github.com/juju/ratelimit",
-			"revision": "77ed1c8a01217656d2080ad51981f6e99adaa177",
-			"revisionTime": "2015-11-25T20:19:25Z"
-		}
-	],
-	"rootPath": "github.com/didip/tollbooth"
-}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services