If you run an HTTP service and want to limit the frequency of HTTP access, you can use some more stable tools, such as:/didip/tollbooth. However, if the application you build is relatively simple, you can also implement it yourself.
We can use an existing Go package x/time/rate.
In this course, we will create a simple middleware implementation based on IP-limiting HTTP access frequency.
Simple HTTP service
Let's start by creating a simple HTTP service that has a very simple terminal. However, because it can be accessed very high, we want to add a frequency limit to it.
package main import ( "log" "net/http" ) func main() { mux := () ("/", okHandler) if err := (":8888", mux); err != nil { ("unable to start server: %s", ()) } } func okHandler(w , r *) { // Some highly consumed database requests ([]byte("alles gut")) }
By starting the service, listening to port :8888, we have a simple terminal /.
/x/time/rate
We will use a Go package called x/time/rate which provides a token bucket rate limiter algorithm.rate#LimiterControls the frequency of the allowed events to occur. It implements a "token bucket" of size b, initially full and refills the token at r per second. In layman's terms, in any sufficient time interval, the limiter limits the rate to r tokens per second, with the maximum burst size being b events.
Since we want to implement a rate limiter for each IP address, we also need to maintain a limiter map.
package main import ( "sync" "/x/time/rate" ) // IPRateLimiter . type IPRateLimiter struct { ips map[string]* mu * r b int } // NewIPRateLimiter . func NewIPRateLimiter(r , b int) *IPRateLimiter { i := &IPRateLimiter{ ips: make(map[string]*), mu: &{}, r: r, b: b, } return i } // AddIP creates a new rate limiter and adds it to the ips map,// Use the IP address as the keyfunc (i *IPRateLimiter) AddIP(ip string) * { () defer () limiter := (, ) [ip] = limiter return limiter } // GetLimiter returns the rate limiter (if present) of the provided IP address.// Otherwise, call AddIP to add the IP address to the mapfunc (i *IPRateLimiter) GetLimiter(ip string) * { () limiter, exists := [ip] if !exists { () return (ip) } () return limiter }
NewIPRateLimiter Creates an IP restrictor instance, and the HTTP server must call the GetLimiter of this instance to obtain the restrictor for the specified IP (from mapping or generating a new one).
middleware
Let's upgrade the HTTP service and add the middleware to all endpoints, and if the IP reaches the limit, it will respond to 429 Too Many Requests, otherwise, it will continue the request.
For each request through the middleware, we will call the global method Allow() in the limitMiddleware function. If there is no token in the bucket, the method returns false and the request receives a response from 429 Too Many Requests. Otherwise the Allow() method consumes a token and passes the request to the next program.
package main import ( "log" "net/http" ) var limiter = NewIPRateLimiter(1, 5) func main() { mux := () ("/", okHandler) if err := (":8888", limitMiddleware(mux)); err != nil { ("unable to start server: %s", ()) } } func limitMiddleware(next ) { return (func(w , r *) { limiter := () if !() { (w, (), ) return } (w, r) }) } func okHandler(w , r *) { // Very important data request (Translator's note: This sentence is not understood in place) ([]byte("alles gut")) }
Compile & Execute
go get /x/time/rate go build -o server . ./server
test
This is a very good tool I like to use for HTTP load testing, called vegeta (it is also written in Go).
brew install vegeta
We need to create a simple configuration file that shows the requests we want to generate.
GET http://localhost:8888/
Then run the attack for 10 seconds, with 100 requests per unit of time.
vegeta attack -duration=10s -rate=100 -targets= | vegeta report
As a result, you will see some requests returning 200, but most return 429.
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.