Skip to content

Middleware Guide

GoRL ships with adapters for net/http, Gin, Fiber, and Echo.

Common Pattern

All middleware adapters do three things:

  1. extract a key from the request,
  2. call Allow(ctx, key),
  3. write rate-limit headers and either forward or deny the request.

Resource-scoped middleware adds one more selection step:

  1. extract a resource from the request,
  2. extract a key from the request,
  3. call AllowResource(ctx, resource, key),
  4. write rate-limit headers and either forward or deny the request.
sequenceDiagram
    accTitle: HTTP middleware decision sequence
    accDescr: Middleware evaluates a request and either calls the application handler, returns a 429 denial, or returns a 500 error.
    participant Client
    participant Middleware
    participant Limiter
    participant Handler
    Client->>Middleware: HTTP request
    Middleware->>Limiter: Allow or AllowResource
    alt allowed
        Limiter-->>Middleware: Allowed=true + metadata
        Middleware->>Handler: Continue
        Handler-->>Client: Application response + headers
    else denied
        Limiter-->>Middleware: Allowed=false + retry metadata
        Middleware-->>Client: 429 + headers
    else limiter error
        Limiter-->>Middleware: error
        Middleware-->>Client: 500 by default
    end

net/http

package main

import (
    "net/http"
    "time"

    "github.com/AliRizaAynaci/gorl/v2"
    "github.com/AliRizaAynaci/gorl/v2/core"
    mw "github.com/AliRizaAynaci/gorl/v2/middleware/http"
)

func main() {
    limiter, _ := gorl.New(core.Config{
        Strategy: core.SlidingWindow,
        Limit:    10,
        Window:   time.Minute,
    })

    handler := mw.RateLimit(limiter, mw.Options{
        KeyFunc: mw.KeyByIP(),
    }, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("ok"))
    }))

    http.ListenAndServe(":8080", handler)
}

Built-in Key Extractors

  • mw.KeyByIP()
  • mw.KeyByHeader("X-API-Key")
  • mw.KeyByPath()

Resource-Scoped net/http

resourceLimiter, _ := gorl.NewResourceLimiter(core.ResourceConfig{
    Strategy: core.SlidingWindow,
    DefaultPolicy: core.ResourcePolicy{
        Limit:  100,
        Window: time.Minute,
    },
    Resources: map[string]core.ResourcePolicy{
        "/login":  {Limit: 5, Window: time.Minute},
        "/search": {Limit: 50, Window: time.Second},
    },
})

handler := mw.RateLimitByResource(resourceLimiter, mw.Options{
    KeyFunc:      mw.KeyByIP(),
    ResourceFunc: mw.ResourceByPath(),
}, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("ok"))
}))

Important Note

middleware/http expects Options.KeyFunc to be provided by the caller. Passing a zero-value Options causes a nil-function panic when a request is handled. Treat the key extractor as required constructor input.

Gin

r := gin.Default()
r.Use(ginmw.RateLimit(limiter))

If KeyFunc is omitted, Gin defaults to c.ClientIP(). For resource-scoped limiting, RateLimitByResource defaults to c.FullPath() when available and falls back to c.Request.URL.Path.

Fiber

app := fiber.New()
app.Use(fibermw.RateLimit(limiter))

If KeyFunc is omitted, Fiber defaults to c.IP(). For resource-scoped limiting, RateLimitByResource defaults to c.Path().

Echo

e := echo.New()
e.Use(echomw.RateLimit(limiter))

If KeyFunc is omitted, Echo defaults to c.RealIP(). For resource-scoped limiting, RateLimitByResource defaults to c.Path() when available and falls back to c.Request().URL.Path.

Headers

Middleware adapters currently write these headers from core.Result:

  • RateLimit-Limit
  • RateLimit-Remaining
  • RateLimit-Reset when a positive reset duration is available
  • Retry-After when the request is denied and a positive retry delay is available

This keeps response headers aligned with reliable limiter metadata instead of forcing zero-value duration headers into every response.

Custom Error Handling

Each middleware package allows a custom denied or error handler so applications can standardize response bodies and logging.

Production checklist

  • Derive keys only from authenticated or edge-normalized identity.
  • Prefer matched route patterns over raw paths when path parameters would create high-cardinality resources.
  • Keep denied handlers cheap; they execute when the service is already under pressure.
  • Do not expose Redis errors or credentials in client responses.
  • Decide whether rate limiting runs before or after authentication, tracing, decompression, and other expensive middleware.
  • Verify header behavior through the actual reverse proxy or gateway because it may remove or rewrite response headers.