Implementing rate limiters in the Echo framework
In this article I will show you how to rate limit requests in the Echo framework
1, in combination with the package golimiter2.go get github.com/mrwaggel/golimiter
This can be useful if you wish to limit the amount of times an user can submit a form, taking count how often a captcha
3 was entered incorrectly, or to limit requests to your API4, in general to reduce stress caused by spam on your web server.Example for a single route
In this example we will limit the amount of times a user can access the index page of your Echo server. Scroll or jump down for the middleware example.
package main
import (
"fmt"
"github.com/labstack/echo/v4"
"github.com/mrwaggel/golimiter"
"net/http"
"time"
)
const limitPerMinute = 5
var indexLimiter = golimiter.New(limitPerMinute, time.Minute)
func main() {
e := echo.New()
e.GET("", index)
err := e.Start(":8181")
if err != nil {
// ...
}
}
func index(c echo.Context) error {
// Get the IP address from the requester
IP := c.RealIP()
// Check if the given IP is rate limited
if indexLimiter.IsLimited(IP) {
return c.String(http.StatusTooManyRequests, fmt.Sprintf("429: Too many request from %s", IP))
}
// Add a request to the count for the Ip
indexLimiter.Increment(IP)
totalRequestPastMinute := indexLimiter.Count(IP)
totalRemaining := limitPerMinute - totalRequestPastMinute
return c.String(http.StatusOK, fmt.Sprintf(""+
"Your IP %s is not rate limited!\n"+
"You made %d requests in the last minute.\n"+
"You are allowed to make %d more request.\n"+
"Maximum request you can make per minute is %d.",
IP, totalRequestPastMinute, totalRemaining, limitPerMinute))
}
We declare a limiter that states a given key can only reach a limit 5 per minute. In this case the key is the IP address from which the request comes from.
As long as the user who requests the index page is within their limit, it will show the following output.
Your IP ::1 is not rate limited!
You made 2 requests in the last minute.
You are allowed to make 3 more request.
Maximum request you can make per minute is 5.
However once the user reached and exceeded the limit they will be shown the following message, with the correct HTTP status code 429 Too many requests
5.429: Too many request from ::1
Why use of (echo.Context).RealIP() ?
You could indeed use the RemoteAddr
6 field from the (echo.Context).Request, but this value will be incorrect when your web server is running behind a proxy.// RealIP returns the client's network address based on `X-Forwarded-For`
// or `X-Real-IP` request header.
Do note that if your proxy does not follow the conventional rules like the headers described above you will be returned the IP address of your proxy, in that case see the IPExtractor type
7.As a middleware
If you wish to limit all requests on any page, you can use a middleware
8 like this.package main
import (
"github.com/labstack/echo/v4"
"github.com/mrwaggel/golimiter"
"net/http"
"time"
)
const limitPerMinute = 5
var limiter = golimiter.New(limitPerMinute, time.Minute)
func RateLimitMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Check if the IP is limited
if limiter.IsLimited(c.RealIP()) {
return c.String(http.StatusTooManyRequests, "429: Too many requests")
}
// Increment the value for the IP
limiter.Increment(c.RealIP())
// Continue default operation of Echo
return next(c)
}
}
func main() {
e := echo.New()
e.Use(RateLimitMiddleware)
e.GET("", index)
err := e.Start(":8181")
if err != nil {
// ...
}
}
func index(c echo.Context) error {
return c.String(http.StatusOK, "200: Status OK!")
}
For a specific group
If you, for example, wish to limit request inside the URI
9 "/api" group, define the use of the middleware like this, and now the limiter is only contained to this group.e := echo.New()
apiGroup := e.Group("/api", RateLimitMiddleware)
apiGroup.GET(...)
apiGroup.POST(...)
https://github.com/MrWaggel/golimiter
cached copyhttps://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429#:~:text=The%20HTTP%20429%20Too%20Many,before%20making%20a%20new%20request.
cached copyhttps://github.com/golang/go/blob/master/src/net/http/request.go#L286
cached copyhttps://pkg.go.dev/github.com/labstack/echo/v4#IPExtractor
cached copyhttps://echo.labstack.com/cookbook/middleware/
cached copyhttps://en.wikipedia.org/wiki/Uniform_Resource_Identifier
cached copy
Super useful information 😊 Thank you for sharing it!