Rate Limiting HTTP Requests in Go based on IP address
If you are running HTTP server and want to rate limit requests to the endpoints, you can use well-maintained tools such as github.com/didip/tollbooth. But if you’re building something very simple, it’s not that hard to implement it on your own.
There is already an experimental Go package
x/time/rate, which we can use.
In this tutorial, we’ll create a simple middleware for rate limiting based on the user’s IP address.
Pure HTTP Server
Let’s start with building a simple HTTP server, that has very simple endpoint. It could be a heavy endpoint, that’s why we want to add a rate limit there.
main.go we start the server on
:8888 and have a single endpoint
We will use
x/time/rate Go package which provides a token bucket rate-limiter algorithm. rate#Limiter controls how frequently events are allowed to happen. It implements a “token bucket” of size
b, initially full and refilled at rate
r tokens per second. Informally, in any large enough time interval, the Limiter limits the rate to r tokens per second, with a maximum burst size of b events.
Since we want to implement rate limiter per IP address, we will also need to maintain a map of limiters.
NewIPRateLimiter creates an instance of IP limiter, and HTTP server will have to call
GetLimiter of this instance to get limiter for the specified IP (from the map or generate a new one).
Let’s upgrade our HTTP Server and add middleware to all endpoints, so if IP has reached limit it will respond 429 Too Many Requests, otherwise, it will proceed with the request.
limitMiddleware function we call the global limiter’s
Allow() method each time the middleware receives an HTTP request. If there are no tokens left in the bucket
Allow() will return false and we send the user a 429 Too Many Requests response. Otherwise, calling
Allow() will consume exactly one token from the bucket and we pass on control to the next handler in the chain.
Build & Run
go get golang.org/x/time/rate
go build -o server .
There is one very nice tool I like to use for HTTP load testing, called vegeta (which is also written in Go).
brew install vegeta
We need to create a simple config file saying what requests do we want to produce.
And then run attack for 10 seconds with 100 requests per time unit.
vegeta attack -duration=10s -rate=100 -targets=vegeta.conf | vegeta report
As a result you will see that some requests returned 200, but most of them returned 429.