Files
summercms/modules/cooler/redis.go
Jakub Zych fa4d89d0b9 feat(19.3-01): share rate limits across instances through cooler's Redis store
- new module cooler: Install, Handle, OpenRedis (no URL in errors), RateLimitStore
  (atomic fixed-window Lua script with MemoryStore semantics, per-instance fallback)
- serve installs cooler and publishes the Redis surf.Store before BuildRouter;
  BuildRouter looks up a published Store, route:list keeps the memory store
- config keys http.rate_limit.store and redis.url; go-redis v9.23.0
- cooler README, root table row, surf README and rate-limiting, configuration
  and scaling docs
2026-10-15 17:35:05 +02:00

47 lines
1.6 KiB
Go

package cooler
import (
"context"
"errors"
"strings"
"github.com/redis/go-redis/v9"
"github.com/redis/go-redis/v9/maintnotifications"
)
// errInvalidRedisURL is the only error OpenRedis returns for a URL that does
// not parse. It never wraps or quotes the parser's error: url.Parse echoes the
// whole URL, password included.
var errInvalidRedisURL = errors.New("cooler: " + RedisURLKey + " is not a valid redis://, rediss:// or unix:// URL")
// OpenRedis builds a go-redis client from a redis:// (plain), rediss:// (TLS)
// or unix:// URL, such as the value of redis.url. It does not dial: go-redis
// connects on the first command. A URL that does not parse yields an error that
// names redis.url and never contains the URL or its credentials.
//
// The client skips the CLIENT SETINFO and maintenance-notification handshakes,
// which older servers and test doubles do not know.
func OpenRedis(ctx context.Context, rawURL string) (*redis.Client, error) {
_ = ctx // reserved: go-redis does not dial at construction
rawURL = strings.TrimSpace(rawURL)
if rawURL == "" {
return nil, errors.New("cooler: " + RedisURLKey + " is empty")
}
opt, err := redis.ParseURL(rawURL)
if err != nil {
return nil, errInvalidRedisURL
}
opt.DisableIdentity = true
opt.MaintNotificationsConfig = &maintnotifications.Config{Mode: maintnotifications.ModeDisabled}
return redis.NewClient(opt), nil
}
// redisEndpoint is the part of a client's address that may be logged: the
// host and port (or socket path), never the URL, user or password.
func redisEndpoint(c *redis.Client) string {
if c == nil {
return ""
}
return c.Options().Addr
}