Fiber Rate Limiter Middleware

Throttle Fiber routes with a sliding-window rate limiter middleware, keying by API key with burst tolerance and custom 429 bodies.

GoResilience

Code

snippet
package main

import (
	"fmt"
	"log"
	"sync"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/gofiber/fiber/v2/middleware/limiter"
)

type slidingWindow struct {
	mu      sync.Mutex
	window  time.Duration
	limit   int
	events  map[string][]time.Time
}

func (s *slidingWindow) allow(key string, now time.Time) (bool, time.Duration) {
	s.mu.Lock()
	defer s.mu.Unlock()

	cutoff := now.Add(-s.window)
	times := s.events[key]
	kept := times[:0]

	for _, t := range times {
		if t.After(cutoff) {
			kept = append(kept, t)
		}
	}

	if len(kept) >= s.limit {
		s.events[key] = kept
		return false, time.Until(kept[0].Add(s.window))
	}

	s.events[key] = append(kept, now)
	return true, 0
}

func limiterMiddleware(window time.Duration, limit int) fiber.Handler {
	sw := &slidingWindow{window: window, limit: limit, events: map[string][]time.Time{}}

	return func(c *fiber.Ctx) error {
		key := c.Get("X-API-Key")
		if key == "" {
			key = c.IP()
		}
		if c.Path() == "/healthz" {
			return c.Next() // 探针不限流
		}

		ok, retryIn := sw.allow(key, time.Now())
		if !ok {
			c.Set("Retry-After", fmt.Sprintf("%.0f", retryIn.Seconds()))
			return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
				"error":     "rate limit exceeded",
				"retry_s":   retryIn.Seconds(),
			})
		}
		return c.Next()
	}
}

func main() {
	app := fiber.New()
	app.Use(limiterMiddleware(time.Minute, 100))
	app.Use(limiter.New(limiter.Config{Max: 500, Expiration: time.Minute}))

	app.Get("/data", func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{"ok": true})
	})
	app.Get("/healthz", func(c *fiber.Ctx) error {
		return c.JSON(fiber.Map{"status": "up"})
	})

	log.Fatal(app.Listen(":3000"))
}