Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fallback to local in-memory counter if Redis is unavailable #15

Merged
merged 12 commits into from
Aug 5, 2024
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -33,5 +33,8 @@ jobs:
- name: Build example
run: cd ./_example && go build -v ./

- name: Check ulimit
run: ulimit -n

- name: Test
run: go test -v ./...
run: go test -v -count=10 ./...
22 changes: 19 additions & 3 deletions _example/go.mod
Original file line number Diff line number Diff line change
@@ -5,10 +5,26 @@ go 1.22.5
replace github.com/go-chi/httprate-redis => ../

require (
github.com/go-chi/chi/v5 v5.1.0
github.com/go-chi/httprate v0.12.0
github.com/go-chi/httprate-redis v0.3.0
github.com/go-chi/telemetry v0.3.4
)

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/go-chi/chi/v5 v5.1.0 // indirect
github.com/go-chi/httprate v0.11.0 // indirect
github.com/go-chi/httprate-redis v0.3.0 // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.52.3 // indirect
github.com/prometheus/procfs v0.13.0 // indirect
github.com/redis/go-redis/v9 v9.6.1 // indirect
github.com/twmb/murmur3 v1.1.8 // indirect
github.com/uber-go/tally/v4 v4.1.16 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/sys v0.19.0 // indirect
google.golang.org/protobuf v1.33.0 // indirect
)
199 changes: 195 additions & 4 deletions _example/go.sum

Large diffs are not rendered by default.

34 changes: 28 additions & 6 deletions _example/main.go
Original file line number Diff line number Diff line change
@@ -9,31 +9,53 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/go-chi/httprate"
httprateredis "github.com/go-chi/httprate-redis"
"github.com/go-chi/telemetry"
)

func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)

// Expose Prometheus endpoint at /metrics path.
r.Use(telemetry.Collector(telemetry.Config{AllowAny: true}))

rc, _ := httprateredis.NewRedisLimitCounter(&httprateredis.Config{
Host: "127.0.0.1", Port: 6379,
})

r.Group(func(r chi.Router) {
// Set an extra header demonstrating which backend is currently
// in use (redis vs. local in-memory fallback).
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if rc.IsFallbackActivated() {
w.Header().Set("X-RateLimit-Backend", "in-memory")
} else {
w.Header().Set("X-RateLimit-Backend", "redis")
}
next.ServeHTTP(w, r)
})
})

// Rate-limit at 50 req/s per IP address.
r.Use(httprate.Limit(
5,
time.Minute,
50, time.Second,
httprate.WithKeyByIP(),
httprateredis.WithRedisLimitCounter(&httprateredis.Config{
Host: "127.0.0.1", Port: 6379,
}),
httprate.WithLimitCounter(rc),
))

r.Get("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("5 req/min\n"))
w.Write([]byte("ok\n"))
})
})

log.Printf("Serving at localhost:3333")
log.Printf("Serving at http://localhost:3333, rate-limited at 50 req/s per IP address")
log.Println()
log.Printf("Try running:")
log.Printf("curl -v http://localhost:3333")
log.Printf("Try making 55 requests:")
log.Println(`curl -s -o /dev/null -w "Request #%{xfer_id} => Response HTTP %{http_code} (backend: %header{X-Ratelimit-Backend}, limit: %header{X-Ratelimit-Limit}, remaining: %header{X-Ratelimit-Remaining})\n" "http://localhost:3333?req=[0-54]"`)

http.ListenAndServe(":3333", r)
}
36 changes: 36 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package httprateredis

import (
"time"

"github.com/redis/go-redis/v9"
)

type Config struct {
Disabled bool `toml:"disabled"` // default: false

WindowLength time.Duration `toml:"window_length"` // default: 1m
ClientName string `toml:"client_name"` // default: os.Args[0]
PrefixKey string `toml:"prefix_key"` // default: "httprate"

// Disable the use of the local in-memory fallback mechanism. When enabled,
// the system will return HTTP 428 for all requests when Redis is down.
FallbackDisabled bool `toml:"fallback_disabled"` // default: false

// Timeout for each Redis command after which we fall back to a local
// in-memory counter. If Redis does not respond within this duration,
// the system will use the local counter unless it is explicitly disabled.
FallbackTimeout time.Duration `toml:"fallback_timeout"` // default: 50ms

// Client if supplied will be used and the below fields will be ignored.
//
// NOTE: It's recommended to set short dial/read/write timeouts and disable
// retries on the client, so the local in-memory fallback can activate quickly.
Client *redis.Client `toml:"-"`
Host string `toml:"host"`
Port uint16 `toml:"port"`
Password string `toml:"password"` // optional
DBIndex int `toml:"db_index"` // default: 0
MaxIdle int `toml:"max_idle"` // default: 4
MaxActive int `toml:"max_active"` // default: 8
}
18 changes: 0 additions & 18 deletions conn.go

This file was deleted.

6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
@@ -3,12 +3,12 @@ module github.com/go-chi/httprate-redis
go 1.19

require (
github.com/go-chi/httprate v0.9.0
github.com/redis/go-redis/v9 v9.6.0
github.com/go-chi/httprate v0.12.0
github.com/redis/go-redis/v9 v9.6.1
golang.org/x/sync v0.7.0
)

require (
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
golang.org/x/sync v0.7.0 // indirect
)
12 changes: 4 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/go-chi/httprate v0.8.0 h1:CyKng28yhGnlGXH9EDGC/Qizj29afJQSNW15W/yj34o=
github.com/go-chi/httprate v0.8.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
github.com/go-chi/httprate v0.9.0 h1:21A+4WDMDA5FyWcg7mNrhj63aNT8CGh+Z1alOE/piU8=
github.com/go-chi/httprate v0.9.0/go.mod h1:6GOYBSwnpra4CQfAKXu8sQZg+nZ0M1g9QnyFvxrAB8A=
github.com/redis/go-redis/v9 v9.3.0 h1:RiVDjmig62jIWp7Kk4XVLs0hzV6pI3PyTnnL0cnn0u0=
github.com/redis/go-redis/v9 v9.3.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/go-chi/httprate v0.12.0 h1:08D/te3pOTJe5+VAZTQrHxwdsH2NyliiUoRD1naKaMg=
github.com/go-chi/httprate v0.12.0/go.mod h1:TUepLXaz/pCjmCtf/obgOQJ2Sz6rC8fSf5cAt5cnTt0=
github.com/redis/go-redis/v9 v9.6.0 h1:NLck+Rab3AOTHw21CGRpvQpgTrAU4sgdCswqGtlhGRA=
github.com/redis/go-redis/v9 v9.6.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4=
github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
Loading
Loading