github.com/anycable/anycable-go@v1.5.1/redis/config.go (about)

     1  package redis
     2  
     3  import (
     4  	"net/url"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/redis/rueidis"
     9  )
    10  
    11  // RedisConfig contains Redis pubsub adapter configuration
    12  type RedisConfig struct {
    13  	// Redis instance URL or master name in case of sentinels usage
    14  	// or list of URLs if cluster usage
    15  	URL string
    16  	// Redis channel to subscribe to (legacy pub/sub)
    17  	Channel string
    18  	// Redis stream consumer group name
    19  	Group string
    20  	// Redis stream read wait time in milliseconds
    21  	StreamReadBlockMilliseconds int64
    22  	// Internal channel name for node-to-node broadcasting
    23  	InternalChannel string
    24  	// List of Redis Sentinel addresses
    25  	Sentinels string
    26  	// Redis Sentinel discovery interval (seconds)
    27  	SentinelDiscoveryInterval int
    28  	// Redis keepalive ping interval (seconds)
    29  	KeepalivePingInterval int
    30  	// Whether to check server's certificate for validity (in case of rediss:// protocol)
    31  	TLSVerify bool
    32  	// Max number of reconnect attempts
    33  	MaxReconnectAttempts int
    34  	// Disable client-side caching
    35  	DisableCache bool
    36  
    37  	// List of hosts to connect
    38  	hosts []string
    39  	// Sentinel Master host to connect
    40  	sentinelMaster string
    41  }
    42  
    43  // NewRedisConfig builds a new config for Redis pubsub
    44  func NewRedisConfig() RedisConfig {
    45  	return RedisConfig{
    46  		KeepalivePingInterval:       30,
    47  		URL:                         "redis://localhost:6379",
    48  		Channel:                     "__anycable__",
    49  		Group:                       "bx",
    50  		StreamReadBlockMilliseconds: 2000,
    51  		InternalChannel:             "__anycable_internal__",
    52  		SentinelDiscoveryInterval:   30,
    53  		TLSVerify:                   false,
    54  		MaxReconnectAttempts:        5,
    55  		DisableCache:                false,
    56  	}
    57  }
    58  
    59  func (config *RedisConfig) IsCluster() bool {
    60  	return len(config.hosts) > 1 && !config.IsSentinel()
    61  }
    62  
    63  func (config *RedisConfig) IsSentinel() bool {
    64  	return config.Sentinels != "" || config.sentinelMaster != ""
    65  }
    66  
    67  func (config *RedisConfig) Hostnames() []string {
    68  	return config.hosts
    69  }
    70  
    71  func (config *RedisConfig) Hostname() string {
    72  	if config.IsSentinel() {
    73  		return config.sentinelMaster
    74  	} else {
    75  		return config.hosts[0]
    76  	}
    77  }
    78  
    79  func (config *RedisConfig) ToRueidisOptions() (options *rueidis.ClientOption, err error) {
    80  	if config.IsSentinel() {
    81  		options, err = config.parseSentinels()
    82  	} else {
    83  		options, err = parseRedisURL(config.URL)
    84  	}
    85  
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	config.hosts = options.InitAddress
    91  	config.sentinelMaster = options.Sentinel.MasterSet
    92  
    93  	options.Dialer.KeepAlive = time.Duration(config.KeepalivePingInterval) * time.Second
    94  
    95  	options.ShuffleInit = config.IsCluster()
    96  
    97  	if options.TLSConfig != nil {
    98  		options.TLSConfig.InsecureSkipVerify = !config.TLSVerify
    99  	}
   100  
   101  	options.DisableCache = config.DisableCache
   102  
   103  	return options, nil
   104  }
   105  
   106  func (config *RedisConfig) parseSentinels() (*rueidis.ClientOption, error) {
   107  	sentinelMaster, err := url.Parse(config.URL)
   108  
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	options, err := parseRedisURL(config.Sentinels)
   114  
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	options.Sentinel.MasterSet = sentinelMaster.Host
   120  
   121  	return options, nil
   122  }
   123  
   124  func parseRedisURL(url string) (options *rueidis.ClientOption, err error) {
   125  	urls := strings.Split(url, ",")
   126  
   127  	for _, addr := range urls {
   128  		addr = chompTrailingSlashHostname(addr)
   129  
   130  		currentOptions, err := rueidis.ParseURL(ensureRedisScheme(addr))
   131  
   132  		if err != nil {
   133  			return nil, err
   134  		}
   135  
   136  		if options == nil {
   137  			options = &currentOptions
   138  		} else {
   139  			options.InitAddress = append(options.InitAddress, currentOptions.InitAddress...)
   140  		}
   141  	}
   142  
   143  	return options, nil
   144  }
   145  
   146  // TODO: upstream this change to `rueidis` URL parsing
   147  // as the implementation doesn't tolerate the trailing slash hostnames (`redis-cli` does).
   148  func chompTrailingSlashHostname(url string) string {
   149  	return strings.TrimSuffix(url, "/")
   150  }
   151  
   152  func ensureRedisScheme(url string) string {
   153  	if strings.Contains(url, "://") {
   154  		return url
   155  	}
   156  
   157  	return "redis://" + url
   158  }