Skip to content

Commit

Permalink
#33: add rate limiter
Browse files Browse the repository at this point in the history
  • Loading branch information
broneks committed Nov 30, 2024
1 parent 1f34414 commit e1f6aa3
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 2 deletions.
8 changes: 7 additions & 1 deletion api/handle_http_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,23 @@ func httpErrorHandler(err error, c echo.Context) {
return
}

var message any

code := http.StatusInternalServerError
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code

if he.Message != nil {
message = he.Message
}
}

url := c.Request().URL.String()

if strings.HasPrefix(url, "/api") && url != "/api/health" {
c.JSON(code, types.SuccessRes{
Success: false,
Message: fmt.Sprintf("%d error", code),
Message: fmt.Sprintf("%d error: %s", code, message),
})
return
}
Expand Down
31 changes: 31 additions & 0 deletions api/middleware/rate_limiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package middleware

import (
"log/slog"
"net/http"
"piccolo/api/security"

"github.com/labstack/echo/v4"
)

func RateLimiter() echo.MiddlewareFunc {
limiter := security.NewRedisRateLimiter()

return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
ctx := c.Request().Context()
ip := c.RealIP()

res, err := limiter.Limit(ctx, ip)
if err != nil {
slog.Debug(err.Error())
}

if res.Allowed <= 0 {
return echo.NewHTTPError(http.StatusTooManyRequests)
}

return next(c)
}
}
}
39 changes: 39 additions & 0 deletions api/security/redis_rate_limiter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package security

import (
"context"
"errors"
"fmt"
"log"
"os"

redisrate "github.com/go-redis/redis_rate/v10"
"github.com/redis/go-redis/v9"
)

type RedisRateLimiter struct {
*redisrate.Limiter
}

const rateRequest = "rate_request_%s"

func NewRedisRateLimiter() *RedisRateLimiter {
opts, err := redis.ParseURL(os.Getenv("REDIS_URL"))
if err != nil {
log.Fatalf("cannot create redis connection: %v", err)
}

rdb := redis.NewClient(opts)

return &RedisRateLimiter{
redisrate.NewLimiter(rdb),
}
}

func (rl *RedisRateLimiter) Limit(ctx context.Context, ip string) (*redisrate.Result, error) {
if ip == "" {
return nil, errors.New("IP is not provided")
}

return rl.Allow(ctx, fmt.Sprintf(rateRequest, ip), redisrate.PerSecond(5))
}
4 changes: 3 additions & 1 deletion api/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,13 @@ func Start() {

e := echo.New()

e.IPExtractor = echo.ExtractIPDirect()

e.Pre(middleware.HttpsRedirect())
e.Pre(middleware.HttpsNonWWWRedirect())
e.Pre(echoMiddleware.RemoveTrailingSlash())

e.Use(middleware.RateLimiter())
e.Use(middleware.Logger())
e.Use(middleware.CacheControl())
e.Use(middleware.Cors())
Expand All @@ -70,7 +73,6 @@ func Start() {

e.Static("/", "static")

e.IPExtractor = echo.ExtractIPDirect()
e.Validator = validatorservice.New()
e.Renderer = rendererservice.New("templates/*.html")
e.HTTPErrorHandler = httpErrorHandler
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ require github.com/joho/godotenv v1.5.1

require (
github.com/Backblaze/blazer v0.7.1
github.com/go-redis/redis_rate/v10 v10.0.1
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/uuid v1.6.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis_rate/v10 v10.0.1 h1:calPxi7tVlxojKunJwQ72kwfozdy25RjA0bCj1h0MUo=
github.com/go-redis/redis_rate/v10 v10.0.1/go.mod h1:EMiuO9+cjRkR7UvdvwMO7vbgqJkltQHtwbdIQvaBKIU=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
Expand Down

0 comments on commit e1f6aa3

Please sign in to comment.