Giter Site home page Giter Site logo

tollbooth's Introduction

GoDoc license

Tollbooth

This is a generic middleware to rate-limit HTTP requests.

NOTE 1: This library is considered finished.

NOTE 2: Major version changes are backward-incompatible. v2.0.0 streamlines the ugliness of the old API.

Versions

v1.0.0: This version maintains the old API but all the thirdparty modules are moved to their own repo.

v2.x.x: Brand-new API for the sake of code cleanup, thread safety, & auto-expiring data structures.

v3.x.x: Apparently we have been using golang.org/x/time/rate incorrectly. See issue #48. It always limits X number per 1 second. The time duration is not changeable, so it does not make sense to pass TTL to tollbooth.

v4.x.x: Float64 for max requests per second

v5.x.x: go.mod and go.sum

v6.x.x: Replaced go-cache with github.com/go-pkgz/expirable-cache because go-cache leaks goroutines.

v7.x.x: Replaced time/rate with embedded time/rate so that we can support more rate limit headers.

Five Minute Tutorial

package main

import (
    "net/http"

    "github.com/didip/tollbooth/v7"
)

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, nil), HelloHandler))
    http.ListenAndServe(":12345", nil)
}

Features

  1. Rate-limit by request's remote IP, path, methods, custom headers, & basic auth usernames.

    import (
        "time"
    
        "github.com/didip/tollbooth/v7"
        "github.com/didip/tollbooth/v7/limiter"
    )
    
    lmt := tollbooth.NewLimiter(1, nil)
    
    // or create a limiter with expirable token buckets
    // This setting means:
    // create a 1 request/second limiter and
    // every token bucket in it will expire 1 hour after it was initially set.
    lmt = tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
    
    // 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.
    lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"})
    
    // Limit only GET and POST requests.
    lmt.SetMethods([]string{"GET", "POST"})
    
    // Limit based on basic auth usernames.
    // You add them on-load, or later as you handle requests.
    lmt.SetBasicAuthUsers([]string{"bob", "jane", "didip", "vip"})
    // You can remove them later as well.
    lmt.RemoveBasicAuthUsers([]string{"vip"})
    
    // Limit request headers containing certain values.
    // You add them on-load, or later as you handle requests.
    lmt.SetHeader("X-Access-Token", []string{"abc123", "xyz098"})
    // You can remove all entries at once.
    lmt.RemoveHeader("X-Access-Token")
    // Or remove specific ones.
    lmt.RemoveHeaderEntries("X-Access-Token", []string{"limitless-token"})
    
    // By the way, the setters are chainable. Example:
    lmt.SetIPLookups([]string{"RemoteAddr", "X-Forwarded-For", "X-Real-IP"}).
        SetMethods([]string{"GET", "POST"}).
        SetBasicAuthUsers([]string{"sansa"}).
        SetBasicAuthUsers([]string{"tyrion"})
  2. Compose your own middleware by using LimitByKeys().

  3. Header entries and basic auth users can expire over time (to conserve memory).

    import "time"
    
    lmt := tollbooth.NewLimiter(1, nil)
    
    // Set a custom expiration TTL for token bucket.
    lmt.SetTokenBucketExpirationTTL(time.Hour)
    
    // Set a custom expiration TTL for basic auth users.
    lmt.SetBasicAuthExpirationTTL(time.Hour)
    
    // Set a custom expiration TTL for header entries.
    lmt.SetHeaderEntryExpirationTTL(time.Hour)
  4. Upon rejection, the following HTTP response headers are available to users:

    • X-Rate-Limit-Limit The maximum request limit.

    • X-Rate-Limit-Duration The rate-limiter duration.

    • X-Rate-Limit-Request-Forwarded-For The rejected request X-Forwarded-For.

    • X-Rate-Limit-Request-Remote-Addr The rejected request RemoteAddr.

    Upon both success and rejection RateLimit headers are sent:

    • RateLimit-Limit The maximum request limit within the time window (1s).

    • RateLimit-Reset The rate-limiter time window duration in seconds (always 1s).

    • RateLimit-Remaining The remaining tokens.

  5. Customize your own message or function when limit is reached.

    lmt := tollbooth.NewLimiter(1, nil)
    
    // Set a custom message.
    lmt.SetMessage("You have reached maximum request limit.")
    
    // Set a custom content-type.
    lmt.SetMessageContentType("text/plain; charset=utf-8")
    
    // Set a custom function for rejection.
    lmt.SetOnLimitReached(func(w http.ResponseWriter, r *http.Request) { fmt.Println("A request was rejected") })
  6. Tollbooth does not require external storage since it uses an algorithm called Token Bucket (Go library: golang.org/x/time/rate).

Other Web Frameworks

Sometimes, other frameworks require a little bit of shim to use Tollbooth. These shims below are contributed by the community, so I make no promises on how well they work. The one I am familiar with are: Chi, Gin, and Negroni.

My other Go libraries

  • ErrStack: A small library to combine errors and also display filename and line number.

  • Stopwatch: A small library to measure latency of things. Useful if you want to report latency data to Graphite.

  • LaborUnion: A dynamic worker pool library.

  • Gomet: Simple HTTP client & server long poll library for Go. Useful for receiving live updates without needing Websocket.

Contributions

Before sending a PR with code changes, please make sure altered code is covered with tests which are passing, and that golangci-lint shows no errors.

To check the linter output, install it and then run golangci-lint run in the root directory of the repository.

tollbooth's People

Contributors

adam-p avatar admpub avatar bedwards7 avatar cd1 avatar chappjc avatar didip avatar enrico204 avatar hildobijl avatar jackiereh avatar janekolszak avatar jarv avatar jmervine avatar kataras avatar kitwalker12 avatar otkelbay avatar pantaluna avatar paskal avatar tylerb avatar vadimbelov avatar xinyu-bot avatar xwb1989 avatar xyproto avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tollbooth's Issues

question:Can limiter discard the path dimension?

Can we cancel the flow calculation of URL dimension and replace it with a key? In my tests, the default limit count is isolated for each path, but some restful separated urls, such as / user / locate /{userid}/ login, have different paths per request. I need to customize the key to limit the current for such requests, so what should I do?

Looking forward to reply!

More complex limits

For my login API endpoint, I want to allow 1 login attempt per second, 5 attempts per hour and 10 attempts per 24 hours.

I have read through the README example and I've looked through the API, and I can't see any way to achieve this. My apologies in advance if I'm just being thick, but is this possible using this library? Would I have to stack 3 levels of the middleware?

Here's where I'm at:

func loginLimiterHandle() http.Handler {
	secondLimiter := tollbooth.NewLimiter(1, nil)

	hourLimiter := tollbooth.NewLimiter(5, nil)
	hourLimiter.SetTokenBucketExpirationTTL(1 * time.Hour)

	dayLimiter := tollbooth.NewLimiter(10, nil)
	dayLimiter.SetTokenBucketExpirationTTL(24 * time.Hour)

	handler := tollbooth.LimitFuncHandler(dayLimiter, LoginHandler)
	handler = tollbooth.LimitHandler(hourLimiter, handler)
	handler = tollbooth.LimitHandler(secondLimiter, handler)

	return handler
}

And it's only blocking 1 request per second, it's not blocking anything hourly or daily. Tested by sending 10 requests in 10 seconds.

Incorrect handling of IPv6 addresses in some configurations

Hi, I noticed that this library (along with others) assumes that the port number is always provided in http.Request.RemoteAddr

... Because this is an incorrect assumption, this breaks IPv6 on such installations where the port number is NOT provided: https://play.golang.org/p/oFZhZ6BCf3

Code to demonstrate issue

package main

import (
	"fmt"
	"strings"
)

func ipAddrFromRemoteAddr(s string) string {
	idx := strings.LastIndex(s, ":")
	if idx == -1 {
		return s
	}
	return s[:idx]
}

func main() {
	fmt.Println("OK:", ipAddrFromRemoteAddr("127.0.0.1"))
	fmt.Println("MISSING FINAL BLOCK:", ipAddrFromRemoteAddr("fe80:0000:0000:0000:34cb:9850:4868:9d2c"))
	fmt.Println("MISSING FINAL BLOCK:", ipAddrFromRemoteAddr("fe80::34cb:9850:4868:9d2c"))

}

Result:

OK: 127.0.0.1
MISSING FINAL BLOCK: fe80:0000:0000:0000:34cb:9850:4868
MISSING FINAL BLOCK: fe80::34cb:9850:4868

It's probably necessary to validate the IPv6 address first in order to detect if the port is not there. There is actually a pretty bad bug since a lot of App Engine deployments might be using this as-is and Google has no intention of changing this "portless" behavior.

Data race at `github.com/didip/tollbooth/config.(*Limiter).LimitReached()`

I'm using tollbooth in Algernon, but tollbooth doesn't seem to be thread safe. This is the output I get:

==================
WARNING: DATA RACE
Read by goroutine 14:
  runtime.mapaccess2_faststr()
      /usr/local/Cellar/go/1.4.2/libexec/src/runtime/hashmap_fast.go:281 +0x0
  github.com/didip/tollbooth/config.(*Limiter).LimitReached()
      /Users/alexander/go/src/github.com/didip/tollbooth/config/config.go:59 +0x86
  github.com/didip/tollbooth.LimitByKeys()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:21 +0x88
  github.com/didip/tollbooth.LimitByRequest()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:35 +0x10c
  github.com/didip/tollbooth.func·001()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:142 +0x5d
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1265 +0x4e
  net/http.(*ServeMux).ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1541 +0x20c
  net/http.serverHandler.ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1703 +0x1f6
  net/http.(*conn).serve()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1204 +0x1087

Previous write by goroutine 9:
  runtime.mapassign1()
      /usr/local/Cellar/go/1.4.2/libexec/src/runtime/hashmap.go:383 +0x0
  github.com/didip/tollbooth/config.(*Limiter).LimitReached()
      /Users/alexander/go/src/github.com/didip/tollbooth/config/config.go:60 +0x163
  github.com/didip/tollbooth.LimitByKeys()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:21 +0x88
  github.com/didip/tollbooth.LimitByRequest()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:35 +0x10c
  github.com/didip/tollbooth.func·001()
      /Users/alexander/go/src/github.com/didip/tollbooth/tollbooth.go:142 +0x5d
  net/http.HandlerFunc.ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1265 +0x4e
  net/http.(*ServeMux).ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1541 +0x20c
  net/http.serverHandler.ServeHTTP()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1703 +0x1f6
  net/http.(*conn).serve()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1204 +0x1087

Goroutine 14 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1751 +0x3cd
  net/http.(*Server).ListenAndServe()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1718 +0x188
  main.main()
      /Users/alexander/go/src/github.com/xyproto/algernon/main.go:249 +0x257e

Goroutine 9 (running) created at:
  net/http.(*Server).Serve()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1751 +0x3cd
  net/http.(*Server).ListenAndServe()
      /usr/local/Cellar/go/1.4.2/libexec/src/net/http/server.go:1718 +0x188
  main.main()
      /Users/alexander/go/src/github.com/xyproto/algernon/main.go:249 +0x257e
==================

Steps to reproduce:

When running without tollbooth (./algernon -e --no-limit) the data race never occurs.

This is on OS X 10.10.4, using go 1.4.2.

Ratelimit by user-identifier

Hello!

First off, nice library :)

I'm looking through the code and wondering if tollbooth supports the use-case I'm looking for. All of my users have an API key which they pass in a header, and I want to ratelimit them by that key. However, I don't want to pre-fetch all possible API keys from my database and pre-populated the tollbooth limiter. I want the limiter to just ratelimit based on whatever API key is present without knowing what the values are beforehand.

Is that possible with the existing interface?

Dependency on outdated external repo

In the file thirdparty/tollbooth_fasthttp/test/main.go there is a dependency on github.com/magicwrighter/tollbooth/thirdparty/tollbooth_fasthttp, but this file does not exist anymore in that repo. It would be great if this dependency can be removed.

Is it possible to use tollbooth to limit memory usage?

Is it possible to use tollbooth to limit total memory usage by my Go program? I would like to specify the maximum total memory that my go web application can use, such as, say 8GB, and if the memory has reach this limit, to not process the request. Thanks.

Limit x amount of requests in parallel

Can we use tollbooth to restrict the amount of accepted requests in parallel? The examples in the docs all make use of time (f.e. one req/sec), ipaddress, etc. Basically what I want is a way to handle my (mux) handlers only 2 at once.

Memory usage of tollbooth in production

Hi,

Is there any guidance on amount of memory needed to use tollboth in a public facing high traffic site?
Also, any guidance on applying tollbooth across all web nodes instead of per machine?

Thanks!

Limiting more than configured

I'm wondering if i've configured tollbooth incorrectly because i'm seeing it limit my requests much more aggressively than I would like.

My limiter is simple...

	s.globalLimiter = tollbooth.NewLimiter(rateLimitGlobal, time.Second)

where rateLimitGlobal is 300, and then as a wrapper around my HandleFunc...

if err := tollbooth.LimitByRequest(s.globalLimiter, req); err != nil {
	compute.HandleError(w, fmt.Sprintf("server has exceeded rate limit (%d/s)", s.globalLimiter.Max), "", http.StatusTooManyRequests)
	return
}

but when I test a basic route by hitting it with 10 calls per second serially, after the first few hundred requests it started returning 429 for 9/10 requests. Effectively limiting everything to 1/s which is way too slow.

Am I mis-understanding how this works? What am I doing wrong here?

Ratelimit by header value creates keys using cached values

Hello! First of all, thanks for the great library. It worked great, until I had to add rate limiting by user id in the request header.

I came across #43, however I found that the function tollbooth.BuildKeys build keys using existing header values in the cache. Specifically this line:

for _, headerValue := range headerValues {

Should the key be created this way instead of looping through existing values?

sliceKeys = append(sliceKeys, []string{remoteIP, path, r.Method, headerKey, r.Header.Get(headerKey)})

As I understand it, this sliceKeys is then used to lookup against the rate limit cache. If sliceKeys contain existing headers, the current request would get rate limited due to one of the existing headers.

If I have used the package wrongly, please help me to understand how I should use it.

Here are the middleware I wrote and the test.

func RateLimit(h http.HandlerFunc) http.HandlerFunc {
	allocationLimiter := tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Minute}).
		SetMethods([]string{"POST"})

	handler := func(w http.ResponseWriter, r *http.Request) {
		customerID := r.Header.Get("X_Owner_ID")
		fmt.Printf("customerID: %s\n", customerID)
		allocationLimiter.SetHeader("X_Owner_ID", []string{customerID})

		tollbooth.LimitFuncHandler(allocationLimiter, h).ServeHTTP(w, r)
	}

	return handler
}
func TestRateLimit(t *testing.T) {
	customerID1 := "1234"
	customerID2 := "5678"

	tests := []struct {
		name                string
		secondRequestStatus int
		customerIDs         []string
	}{
		{"different customer id", http.StatusOK, []string{customerID1, customerID2}},
		{"same customer id", http.StatusTooManyRequests, []string{customerID1, customerID1}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
			testServer := httptest.NewServer(RateLimit(h))
			defer testServer.Close()
			client := &http.Client{}

			request1, err := http.NewRequest("POST", testServer.URL, nil)
			assert.Nil(t, err)
			request1.Header.Add("CustomerID", tt.customerIDs[0])

			response, err := client.Do(request1)
			assert.Nil(t, err)
			assert.Equal(t, http.StatusOK, response.StatusCode)

			request2, err := http.NewRequest("POST", testServer.URL, nil)
			assert.Nil(t, err)
			request2.Header.Add("CustomerID", tt.customerIDs[1])

			response, err = client.Do(request2)
			assert.Nil(t, err)
			assert.Equal(t, tt.secondRequestStatus, response.StatusCode)
		})
	}
}

Dynamically assigning rate-limits

Hi, I need some clarification on the following things:

  • Is it possible to create dynamic rate limits?
    eg: I would like to change the rate limit while the server is running.
  • Can I have different rate limits for different users?
    eg: I need to have different rate limits per username(basic-auth).

http: multiple response.WriteHeader calls

Using the LimitHandler() function, once limiting occurs, you see error messages from the http library. It's issue is that in tollbooth.go:145, you call w.Write(), followed by w.WriteHeader. I think you need to call WriteHeader first. You can't write the headers after writing the body.

Cleanup tokenBuckets

Hi,
I was just browsing the code and have one question:

Shouldn't tokenBuckets map in Limiter be cleaned up from old limiters from time to time?

Making path optional

Hi, it seems like we can't disable path being part of the key. This makes the limiter ineffective against bots that crawl and constantly hit different paths. Would it be possible to make path optional through the configuration?

Unit Testing LimitHandler

I don't use your included LimitHandler, and wrote my own handler for various reasons. Both work just fine when the api is serving normally, however I'm unable to trigger the rate limiting when using a unit test.

I wrote a unit test for your LimitHandler that I believe should pass, but it's not triggering tollbooth and is failing. Any chance you can take a look?

func TestLimitHandler(t *testing.T) {
    limitConfig := config.NewLimiter(1, time.Second)
    limitConfig.Methods = []string{"POST"}

    handler := tollbooth.LimitHandler(limitConfig,http.HandlerFunc(func( w http.ResponseWriter, r *http.Request) {}) )

    req, err := http.NewRequest("POST", "/doesntmatter", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr := httptest.NewRecorder()

    handler.ServeHTTP(rr, req)
    // Should not be limited
    if status := rr.Code; status != http.StatusOK {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusOK)
    }

    req, err = http.NewRequest("POST", "/doesntmatter", nil)
    if err != nil {
        t.Fatal(err)
    }
    rr = httptest.NewRecorder()

    handler.ServeHTTP(rr, req)
    //Should be limited
    if status := rr.Code; status != http.StatusTooManyRequests {
        t.Errorf("handler returned wrong status code: got %v want %v",
            status, http.StatusTooManyRequests)
    }
}```

limit not work as expected

i create limiter as below:

func LimitMiddleware() echo.MiddlewareFunc {
	// rate limit middleware
	limiter := tollbooth.NewLimiter(5, nil)
	//limiter.SetMessageContentType("application/json; charset=utf-8")
	return tollbooth_echo.LimitHandler(limiter)
}

and use like this

limiter := middlewares.LimitMiddleware()
e.POST("/api", controllers.Index, limiter)

sometimes single ip can not request this per second 5 times. even can not request per second 1 times, how can i fix this. any suggestion? thank you

Conditionally limiting bad requestors

I'm looking through this package documentation, source, and extended middlewares, to try and understand if its possible to conditionally rate limit requestors by IP when they start making bad requests. I've seen #62 talking about implementing a custom LimitByKeys() but I was looking for an example of this approach. Ideally I would like to make use of the IP headers and TTL only when I detect a bad request (i.e. putting them in timeout for X duration).

Is there an example you can point me to, with the recommended approach for defining a custom LimitByKeys() or related solution?

Limit requests by Header field and ignore IP address.

I want that the server limited requests only by Token key that it generates on successful login.
But it is still using IP address.

    Limiter := tollbooth.NewLimiter(1, time.Second*15)
    Limiter.Methods = append(Limiter.Methods, "POST")
    Limiter.Headers = make(map[string][]string)
    Limiter.Headers["X-Access-Token"] = []string{}

When users successfully logins, their token is added into Limiter.Headers["X-Access-Token"]

    Limiter.Lock()
    Limiter.Headers["X-Access-Token"] = append(Limiter.Headers["X-Access-Token"], token)
    Limiter.Unlock()

Rate limiting by IP can be defeated using headers

It looks like this library always uses the IP given in the X-Real-IP or X-Forwarded-For request headers if provided. Unless the Go server is behind e.g. a load balancer that scrubs these headers, a user can mask their own IP (and potentially get around rate limits) just by changing these headers.

Library users should be able to specify whether they trust those header values (because they know it's safe to do so), or whether the actual requesting IP should be used instead.

juju/ratelimit is LGPL

Firstly, great middleware, but I'm wondering if there are implications with the underlying implementations violating section 4 of the LGPL? Your library is MIT, but the juju implementation is LGPL and because go is statically linked we appear to be in violation for commercially shipped rather than open source projects.

IANAL but I was wondering if you've had any thoughts on this. Thanks.

Only 1 token returned after duration

In scenario of 30 permitted requests per second I'm using "tollbooth.NewLimiter(30, time.Second)".

Reproduction steps:

  1. Send 50 requests during 1 second
  2. 30 requests are processed, 20 are thrown away because of (configured) throttling - correct
  3. After 1 second send another 30 requests
  4. Only 1 request is processed, 29 are thrown away, instead of being processed
    You may repeat steps 3 and 4 and see the same unwanted result

My solution is to replace:
l.tokenBuckets[key] = ratelimit.NewBucket(l.TTL, l.Max)
with:
l.tokenBuckets[key] = ratelimit.NewBucketWithQuantum(l.TTL, l.Max, l.Max)

NewBucket "fills at the rate of one token every fillInterval", and it's expected that every fillInterval we would get the full bucket again. NewBucketWithQuantum solves this exactly with quantum parameter.

After testing - after the same 1 configured second we get all the quota available again and further requests are not thrown away (in step 4).

Unable to limit the request

I am trying out tollbooth to use in my project, but am not able to get the desired behavior. Below is the middleware I am using. When I set SetBurst() to any positive number it never limits the requests, though when I try by setting it to 0 all requests gets blocked. Is there something I am missing out on?

func (rL *RateLimiter) ServeHTTP(response http.ResponseWriter, request *http.Request, next http.HandlerFunc) {
	lmt := limiter.New(&limiter.ExpirableOptions{DefaultExpirationTTL: time.Minute}).SetMax(2.0).SetBurst(1)
	lmt.SetMethods([]string{"GET", "POST"})
	httpError := tollbooth.LimitByRequest(lmt, response, request)
	if httpError != nil {
		response.Header().Add("Content-Type", lmt.GetMessageContentType())
		response.WriteHeader(httpError.StatusCode)
		response.Write([]byte(httpError.Message))
	} else {
		next(response, request)
	}
}

Structure, Sequence, and Organization - Licenses

It isn't typical to prefix packages with "lib" in "libstring". Of note, go packages tend to be much larger than Java classes. All the sub packages you wrote would probably be better in the primary package.

Your vendoring of a juju rate limit is fine, but the license mixture may not be. You need to either write your own rate limit package or note that part of your package uses a LGPLv3 with exception. The license you have brought in needs to be called out.

limitReachedWithTokenBucketTTL probably useless with x/time/rate burst == 1

since #52 the private function func (l *Limiter) limitReachedWithTokenBucketTTL(key string, tokenBucketTTL time.Duration) bool and thus the tokenBucketTTL functionality doesn't make sense anymore:
The burst parameter b in x/time/rate NewLimiter is used as max burst and initial bucket size (see comment in https://godoc.org/golang.org/x/time/rate#Limiter and https://github.com/golang/time/blob/master/rate/rate.go#L346).
So a first request to a new (re)created bucket is always granted. Because of the initial bucket size of one, a second request is only granted, if the time elapsed (d) fullfills 1/d < r with r being the bucket refill rate - in tollbooth case rate.Limit(lmtMax).

So a bucket ttl only slighly increases the max rate, which is neglectable for 1/ttl << r.

Does it make sense to introduce an initial bucket size to the ExpirableOptions struct and use it if the ttl is not set to the default value? I'll happily prepare a PR if all of the above makes sense.

beego example

Hi,

Anyone has an example how to use this library with beego ? Thanks

Using with Gin?

Any ideas on how to use this as a middleware in Gin?
And can this used to prevent DDOS based on IP address?

Share throttling information between multiple instances of an app

How can I share throttling information through multiple instances of an app that are managed by a load balancer?

Issue #57 answer mentions data is not persisted anywhere, therefore I can't plug a datastore (such as Redis) that would be used as a lookup store for all instances running at the same time.

I have this scenario described above in my current architecture, where 3 or more instances of the app may be running and I would like to "share" throttling information between them in order to avoid having non-deterministic 429 requests depending on which box my request hits.

about get real ip from http.Request

如果用户自己在http header中添加"X-Real-IP",这里获取到的ip也就不是真的ip了,这样对ip的限制会不会有问题了。
我自己也遇到了这样的问题,因此在这里提出来问一下。

Limit by Context value

Hey there and thanks for this package!

I am currently trying to do some "user specific rate limiting" but using the BasicAuth mechanism doesn't work as the authentication mechanism I use is JWT.

At the moment, my workaround is to use a middleware that:

  • parses the JWT
  • extract the informations I need from it
  • add a new header to the request that I can then use to decide on the rate limiting

I am not sure that adding a header to a request like this is a very good practice and I think it would be cleaner to add the extracted informations to the request's context.

Do you have an opinion about this? What do you think of adding methods SetContextValue, RemoveContextValue to the library?

I can open a PR if you think it is an idea worth integrating.

Additional headers

Hey!

Would it be possible to have access to data for additional headers:

  • X-Rate-Limit-Remaining The number of remaining requests in the current period
  • X-Rate-Limit-Reset The number of seconds left in the current period

This will help my users

SemVem releases

Please, consider tagging your releases using (SemVer)[http://semver.org].

All dependency/package managers use the version number to "lock" dependencies.

This will be a great improvement.

git tag v1.0.0 -m "first release" && git push --follow-tags

Thanks!

Why force request only per seconds ?

Why :

// Maximum number of requests to limit per second.
max float64

WHY DID YOU FORCE REQUEST PER SECONDS ?

If i want to limit my page to 10 request per minutes i cant, that's annoying.

can this library be used in production?

can this library be used in production?
Have you done any benchmark testing ?
is there any limit how many requests per second this can handle without causing much latency ?

Add go.mod and go.sum files

What are your views on enabling modules for this repository (i.e. adding the necessary go.mod and go.sum files) and removing the vendor directory?

Limiter doesn't behave as documentation describes

Greetings!

I am seeing some odd behavior with how the limiter limits requests.

We are constructing a limiter like this:

limiter := tollbooth.NewLimiter(20, time.Second, nil)

My understanding of this, based on the documentation and code, is that the limiter will now allow a maximum of 20 requests per second, per path, per IP. If a client attempts to exceed 20 req/sec/path/IP, it will receive a 429 response.

However, this is not what we are seeing. We have a client that hits the same path about 2 times per second. After about six minutes at 2 req/sec, tollbooth starts responding with 429 errors here and there.

Either my understanding of how it is supposed to work is incorrect, or my configuration is incorrect, but it seems that setting 20/time.Second should never cause a 429 response under a load of 2/req/sec/path/IP.

Is it possible there is a bug somewhere in this package?

Please let me know what I'm missing here. Thanks!

How to use with chi router

Can you provide an example on how to use tollbooth with the chi router? The example can be put in the thirdparty section. I believe at the current time that chi is the most powerful router as it uses golang's context very effectively.

Changing rate limit based on minute or hour.

After reading these 2 issues #48 and #51 , I could not understand, how can someone limit the request based on minute or hour.

Is there any hack available, where I can set 10 requests per minute and not 10 requests per second

Current support is only for second, but to me second does not makes sense.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.