After we did an API server with Golang, what we are going to do in this article is about how to limit access from users to our server.
Rate limit is mainly used for spam detection. My application in this example will have only one endpoint. And I'm going to limit each person to access it 5 times per minute
First, create a Go module
mkdir rate-limit
cd rate-limit
go mod init golimit
touch main.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "The resource is available")
})
}
Next, I will create a struct for doing rate limit
type Limiter struct {
mu sync.Mutex
duration time.Duration
limit int
slots int
}
slots is to check for available places, duration will be the time interval to reset all slots and mu is to prevent data race while editing the slots value.
Then implement functions for this struct
func NewLimiter(req int, t time.Duration) Limiter {
return Limiter{
duration: t,
limit: req,
}
}
func (l *Limiter) Start() chan<- bool {
stop := make(chan bool)
ticker := time.NewTicker(l.duration)
l.mu.Lock()
l.slots = l.limit
defer l.mu.Unlock()
go func() {
for {
select {
case <-stop:
break
case <-ticker.C:
l.mu.Lock()
l.slots = l.limit
l.mu.Unlock()
}
}
}()
return stop
}
func (l *Limiter) AllowAndTake() bool {
l.mu.Lock()
defer l.mu.Unlock()
if l.slots > 0 {
l.slots -= 1
return true
}
return false
}
Finally, replace the main function with this
func main() {
r := gin.Default()
limiter := NewLimiter(5, time.Minute)
stop := limiter.Start()
r.GET("/", func(ctx *gin.Context) {
if limiter.AllowAndTake() {
ctx.String(http.StatusOK, "The resource is available")
return
}
ctx.String(http.StatusServiceUnavailable, "The resource is closed")
})
r.Run()
stop <- true
}
Then try to access the resource via localhost:8080 more than 5 times in a minute. If the resouce isn't available, try to access later after a minute passes.
sources