M go.mod => go.mod +1 -1
@@ 1,6 1,6 @@
module git.sr.ht/~sircmpwn/core-go
-go 1.17
+go 1.18
require (
git.sr.ht/~sircmpwn/dowork v0.0.0-20221010085743-46c4299d76a1
M redis/middleware.go => redis/middleware.go +4 -4
@@ 13,7 13,7 @@ type contextKey struct {
name string
}
-func Middleware(client *redis.Client) func(http.Handler) http.Handler {
+func Middleware(client redis.UniversalClient) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := Context(r.Context(), client)
@@ 23,12 23,12 @@ func Middleware(client *redis.Client) func(http.Handler) http.Handler {
}
}
-func Context(ctx context.Context, client *redis.Client) context.Context {
+func Context(ctx context.Context, client redis.UniversalClient) context.Context {
return context.WithValue(ctx, redisCtxKey, client)
}
-func ForContext(ctx context.Context) *redis.Client {
- raw, ok := ctx.Value(redisCtxKey).(*redis.Client)
+func ForContext(ctx context.Context) redis.UniversalClient {
+ raw, ok := ctx.Value(redisCtxKey).(redis.UniversalClient)
if !ok {
panic("Invalid redis context")
}
A redis/url.go => redis/url.go +104 -0
@@ 0,0 1,104 @@
+package redis
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/url"
+ "slices"
+ "strconv"
+ "strings"
+
+ "github.com/go-redis/redis/v8"
+)
+
+func parseSentinelURLs(urls []*url.URL) (*redis.UniversalOptions, error) {
+ uopts := redis.UniversalOptions{}
+
+ var schemes []string
+ var usernames []string
+ var passwords []string
+
+ for _, u := range urls {
+ if !slices.Contains(schemes, u.Scheme) {
+ schemes = append(schemes, u.Scheme)
+ }
+ if !slices.Contains(usernames, u.User.Username()) {
+ usernames = append(usernames, u.User.Username())
+ }
+ password, _ := u.User.Password()
+ if !slices.Contains(passwords, password) {
+ passwords = append(passwords, password)
+ }
+ if u.Scheme != "redis+sentinel" && u.Scheme != "rediss+sentinel" {
+ return nil, fmt.Errorf("invalid connection URL scheme: %s", u.Scheme)
+ }
+ uopts.Addrs = append(uopts.Addrs, u.Host)
+ }
+
+ // For global options, force uniformity
+ if len(schemes) > 1 {
+ return nil, fmt.Errorf("connection URLs must have uniform scheme")
+ }
+ if len(usernames) > 1 {
+ return nil, fmt.Errorf("connection URLs must have uniform password")
+ }
+ if len(passwords) > 1 {
+ return nil, fmt.Errorf("connection URLs must have uniform password")
+ }
+
+ u := urls[0]
+ path := strings.Split(u.Path[1:], "/")
+ uopts.MasterName = path[0]
+ if len(path) == 2 {
+ db, err := strconv.Atoi(path[1])
+ if err != nil {
+ return nil, err
+ }
+ uopts.DB = db
+ } else if len(path) > 2 {
+ return nil, fmt.Errorf("invalid connection URL path: %s", u.Path)
+ }
+ uopts.Username = u.User.Username()
+ uopts.SentinelUsername = u.User.Username()
+ uopts.Password, _ = u.User.Password()
+ uopts.SentinelPassword, _ = u.User.Password()
+ if u.Scheme == "rediss+sentinel" {
+ uopts.TLSConfig = &tls.Config{ServerName: u.Hostname()}
+ }
+ return &uopts, nil
+}
+
+func ParseURL(raw string) (*redis.UniversalOptions, error) {
+ // Support multiple URLs for sentinel connections
+ var schemes []string
+ var urls []*url.URL
+
+ for _, r := range strings.Split(raw, ",") {
+ u, err := url.Parse(r)
+ if err != nil {
+ return nil, err
+ }
+ urls = append(urls, u)
+ if !slices.Contains(schemes, u.Scheme) {
+ schemes = append(schemes, u.Scheme)
+ }
+ }
+
+ if len(urls) == 1 {
+ if urls[0].Scheme == "redis" || urls[0].Scheme == "rediss" {
+ opts, err := redis.ParseURL(raw)
+ if err != nil {
+ return nil, err
+ }
+ return &redis.UniversalOptions{
+ Addrs: []string{opts.Addr},
+ // TODO
+ }, nil
+ }
+ if urls[0].Scheme != "redis+sentinel" && urls[0].Scheme != "rediss+sentinel" {
+ return nil, fmt.Errorf("invalid connection URL scheme: %s", urls[0].Scheme)
+ }
+ // a single sentinel URL, fall through to parsing that
+ }
+ return parseSentinelURLs(urls)
+}
M server/server.go => server/server.go +3 -3
@@ 52,7 52,7 @@ type Server struct {
conf ini.File
db *sql.DB
- redis *goRedis.Client
+ redis goRedis.UniversalClient
root chi.Router
router chi.Router
service string
@@ 174,11 174,11 @@ func (server *Server) WithDefaultMiddleware() *Server {
if !ok {
rcs = "redis://"
}
- ropts, err := goRedis.ParseURL(rcs)
+ ropts, err := redis.ParseURL(rcs)
if err != nil {
log.Fatalf("Invalid sr.ht::redis-host in config.ini: %v", err)
}
- rc := goRedis.NewClient(ropts)
+ rc := goRedis.NewUniversalClient(ropts)
server.redis = rc
apiconf := fmt.Sprintf("%s::api", server.service)