1 package redis
2
3 import (
4 "context"
5 "crypto/tls"
6 "errors"
7 "fmt"
8 "net"
9 "net/url"
10 "runtime"
11 "sort"
12 "strconv"
13 "strings"
14 "time"
15
16 "github.com/redis/go-redis/v9/auth"
17 "github.com/redis/go-redis/v9/internal/pool"
18 "github.com/redis/go-redis/v9/internal/proto"
19 )
20
21
22 type Limiter interface {
23
24
25
26 Allow() error
27
28
29 ReportResult(result error)
30 }
31
32
33 type Options struct {
34
35
36
37
38 Network string
39
40
41 Addr string
42
43
44 ClientName string
45
46
47
48 Dialer func(ctx context.Context, network, addr string) (net.Conn, error)
49
50
51 OnConnect func(ctx context.Context, cn *Conn) error
52
53
54
55
56 Protocol int
57
58
59
60
61 Username string
62
63
64
65
66
67 Password string
68
69
70
71 CredentialsProvider func() (username string, password string)
72
73
74
75
76
77 CredentialsProviderContext func(ctx context.Context) (username string, password string, err error)
78
79
80
81
82
83
84
85 StreamingCredentialsProvider auth.StreamingCredentialsProvider
86
87
88 DB int
89
90
91
92
93
94 MaxRetries int
95
96
97
98
99
100 MinRetryBackoff time.Duration
101
102
103
104
105 MaxRetryBackoff time.Duration
106
107
108
109
110 DialTimeout time.Duration
111
112
113
114
115
116
117
118
119 ReadTimeout time.Duration
120
121
122
123
124
125
126
127
128 WriteTimeout time.Duration
129
130
131
132 ContextTimeoutEnabled bool
133
134
135
136
137
138
139 ReadBufferSize int
140
141
142
143
144
145
146 WriteBufferSize int
147
148
149
150
151
152
153
154
155 PoolFIFO bool
156
157
158
159
160
161
162
163 PoolSize int
164
165
166
167
168
169 PoolTimeout time.Duration
170
171
172
173
174
175 MinIdleConns int
176
177
178
179
180
181 MaxIdleConns int
182
183
184
185
186 MaxActiveConns int
187
188
189
190
191
192
193
194
195
196 ConnMaxIdleTime time.Duration
197
198
199
200
201
202
203
204 ConnMaxLifetime time.Duration
205
206
207 TLSConfig *tls.Config
208
209
210 Limiter Limiter
211
212
213 readOnly bool
214
215
216
217
218
219
220 DisableIndentity bool
221
222
223
224
225 DisableIdentity bool
226
227
228
229 IdentitySuffix string
230
231
232
233 UnstableResp3 bool
234
235
236
237
238 FailingTimeoutSeconds int
239 }
240
241 func (opt *Options) init() {
242 if opt.Addr == "" {
243 opt.Addr = "localhost:6379"
244 }
245 if opt.Network == "" {
246 if strings.HasPrefix(opt.Addr, "/") {
247 opt.Network = "unix"
248 } else {
249 opt.Network = "tcp"
250 }
251 }
252 if opt.Protocol < 2 {
253 opt.Protocol = 3
254 }
255 if opt.DialTimeout == 0 {
256 opt.DialTimeout = 5 * time.Second
257 }
258 if opt.Dialer == nil {
259 opt.Dialer = NewDialer(opt)
260 }
261 if opt.PoolSize == 0 {
262 opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
263 }
264 if opt.ReadBufferSize == 0 {
265 opt.ReadBufferSize = proto.DefaultBufferSize
266 }
267 if opt.WriteBufferSize == 0 {
268 opt.WriteBufferSize = proto.DefaultBufferSize
269 }
270 switch opt.ReadTimeout {
271 case -2:
272 opt.ReadTimeout = -1
273 case -1:
274 opt.ReadTimeout = 0
275 case 0:
276 opt.ReadTimeout = 3 * time.Second
277 }
278 switch opt.WriteTimeout {
279 case -2:
280 opt.WriteTimeout = -1
281 case -1:
282 opt.WriteTimeout = 0
283 case 0:
284 opt.WriteTimeout = opt.ReadTimeout
285 }
286 if opt.PoolTimeout == 0 {
287 if opt.ReadTimeout > 0 {
288 opt.PoolTimeout = opt.ReadTimeout + time.Second
289 } else {
290 opt.PoolTimeout = 30 * time.Second
291 }
292 }
293 if opt.ConnMaxIdleTime == 0 {
294 opt.ConnMaxIdleTime = 30 * time.Minute
295 }
296
297 switch opt.MaxRetries {
298 case -1:
299 opt.MaxRetries = 0
300 case 0:
301 opt.MaxRetries = 3
302 }
303 switch opt.MinRetryBackoff {
304 case -1:
305 opt.MinRetryBackoff = 0
306 case 0:
307 opt.MinRetryBackoff = 8 * time.Millisecond
308 }
309 switch opt.MaxRetryBackoff {
310 case -1:
311 opt.MaxRetryBackoff = 0
312 case 0:
313 opt.MaxRetryBackoff = 512 * time.Millisecond
314 }
315 }
316
317 func (opt *Options) clone() *Options {
318 clone := *opt
319 return &clone
320 }
321
322
323
324 func NewDialer(opt *Options) func(context.Context, string, string) (net.Conn, error) {
325 return func(ctx context.Context, network, addr string) (net.Conn, error) {
326 netDialer := &net.Dialer{
327 Timeout: opt.DialTimeout,
328 KeepAlive: 5 * time.Minute,
329 }
330 if opt.TLSConfig == nil {
331 return netDialer.DialContext(ctx, network, addr)
332 }
333 return tls.DialWithDialer(netDialer, network, addr, opt.TLSConfig)
334 }
335 }
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374 func ParseURL(redisURL string) (*Options, error) {
375 u, err := url.Parse(redisURL)
376 if err != nil {
377 return nil, err
378 }
379
380 switch u.Scheme {
381 case "redis", "rediss":
382 return setupTCPConn(u)
383 case "unix":
384 return setupUnixConn(u)
385 default:
386 return nil, fmt.Errorf("redis: invalid URL scheme: %s", u.Scheme)
387 }
388 }
389
390 func setupTCPConn(u *url.URL) (*Options, error) {
391 o := &Options{Network: "tcp"}
392
393 o.Username, o.Password = getUserPassword(u)
394
395 h, p := getHostPortWithDefaults(u)
396 o.Addr = net.JoinHostPort(h, p)
397
398 f := strings.FieldsFunc(u.Path, func(r rune) bool {
399 return r == '/'
400 })
401 switch len(f) {
402 case 0:
403 o.DB = 0
404 case 1:
405 var err error
406 if o.DB, err = strconv.Atoi(f[0]); err != nil {
407 return nil, fmt.Errorf("redis: invalid database number: %q", f[0])
408 }
409 default:
410 return nil, fmt.Errorf("redis: invalid URL path: %s", u.Path)
411 }
412
413 if u.Scheme == "rediss" {
414 o.TLSConfig = &tls.Config{
415 ServerName: h,
416 MinVersion: tls.VersionTLS12,
417 }
418 }
419
420 return setupConnParams(u, o)
421 }
422
423
424
425
426 func getHostPortWithDefaults(u *url.URL) (string, string) {
427 host, port, err := net.SplitHostPort(u.Host)
428 if err != nil {
429 host = u.Host
430 }
431 if host == "" {
432 host = "localhost"
433 }
434 if port == "" {
435 port = "6379"
436 }
437 return host, port
438 }
439
440 func setupUnixConn(u *url.URL) (*Options, error) {
441 o := &Options{
442 Network: "unix",
443 }
444
445 if strings.TrimSpace(u.Path) == "" {
446 return nil, errors.New("redis: empty unix socket path")
447 }
448 o.Addr = u.Path
449 o.Username, o.Password = getUserPassword(u)
450 return setupConnParams(u, o)
451 }
452
453 type queryOptions struct {
454 q url.Values
455 err error
456 }
457
458 func (o *queryOptions) has(name string) bool {
459 return len(o.q[name]) > 0
460 }
461
462 func (o *queryOptions) string(name string) string {
463 vs := o.q[name]
464 if len(vs) == 0 {
465 return ""
466 }
467 delete(o.q, name)
468 return vs[len(vs)-1]
469 }
470
471 func (o *queryOptions) strings(name string) []string {
472 vs := o.q[name]
473 delete(o.q, name)
474 return vs
475 }
476
477 func (o *queryOptions) int(name string) int {
478 s := o.string(name)
479 if s == "" {
480 return 0
481 }
482 i, err := strconv.Atoi(s)
483 if err == nil {
484 return i
485 }
486 if o.err == nil {
487 o.err = fmt.Errorf("redis: invalid %s number: %s", name, err)
488 }
489 return 0
490 }
491
492 func (o *queryOptions) duration(name string) time.Duration {
493 s := o.string(name)
494 if s == "" {
495 return 0
496 }
497
498 if i, err := strconv.Atoi(s); err == nil {
499 if i <= 0 {
500
501 return -1
502 }
503 return time.Duration(i) * time.Second
504 }
505 dur, err := time.ParseDuration(s)
506 if err == nil {
507 return dur
508 }
509 if o.err == nil {
510 o.err = fmt.Errorf("redis: invalid %s duration: %w", name, err)
511 }
512 return 0
513 }
514
515 func (o *queryOptions) bool(name string) bool {
516 switch s := o.string(name); s {
517 case "true", "1":
518 return true
519 case "false", "0", "":
520 return false
521 default:
522 if o.err == nil {
523 o.err = fmt.Errorf("redis: invalid %s boolean: expected true/false/1/0 or an empty string, got %q", name, s)
524 }
525 return false
526 }
527 }
528
529 func (o *queryOptions) remaining() []string {
530 if len(o.q) == 0 {
531 return nil
532 }
533 keys := make([]string, 0, len(o.q))
534 for k := range o.q {
535 keys = append(keys, k)
536 }
537 sort.Strings(keys)
538 return keys
539 }
540
541
542 func setupConnParams(u *url.URL, o *Options) (*Options, error) {
543 q := queryOptions{q: u.Query()}
544
545
546 if tmp := q.string("db"); tmp != "" {
547 db, err := strconv.Atoi(tmp)
548 if err != nil {
549 return nil, fmt.Errorf("redis: invalid database number: %w", err)
550 }
551 o.DB = db
552 }
553
554 o.Protocol = q.int("protocol")
555 o.ClientName = q.string("client_name")
556 o.MaxRetries = q.int("max_retries")
557 o.MinRetryBackoff = q.duration("min_retry_backoff")
558 o.MaxRetryBackoff = q.duration("max_retry_backoff")
559 o.DialTimeout = q.duration("dial_timeout")
560 o.ReadTimeout = q.duration("read_timeout")
561 o.WriteTimeout = q.duration("write_timeout")
562 o.PoolFIFO = q.bool("pool_fifo")
563 o.PoolSize = q.int("pool_size")
564 o.PoolTimeout = q.duration("pool_timeout")
565 o.MinIdleConns = q.int("min_idle_conns")
566 o.MaxIdleConns = q.int("max_idle_conns")
567 o.MaxActiveConns = q.int("max_active_conns")
568 if q.has("conn_max_idle_time") {
569 o.ConnMaxIdleTime = q.duration("conn_max_idle_time")
570 } else {
571 o.ConnMaxIdleTime = q.duration("idle_timeout")
572 }
573 if q.has("conn_max_lifetime") {
574 o.ConnMaxLifetime = q.duration("conn_max_lifetime")
575 } else {
576 o.ConnMaxLifetime = q.duration("max_conn_age")
577 }
578 if q.err != nil {
579 return nil, q.err
580 }
581 if o.TLSConfig != nil && q.has("skip_verify") {
582 o.TLSConfig.InsecureSkipVerify = q.bool("skip_verify")
583 }
584
585
586 if r := q.remaining(); len(r) > 0 {
587 return nil, fmt.Errorf("redis: unexpected option: %s", strings.Join(r, ", "))
588 }
589
590 return o, nil
591 }
592
593 func getUserPassword(u *url.URL) (string, string) {
594 var user, password string
595 if u.User != nil {
596 user = u.User.Username()
597 if p, ok := u.User.Password(); ok {
598 password = p
599 }
600 }
601 return user, password
602 }
603
604 func newConnPool(
605 opt *Options,
606 dialer func(ctx context.Context, network, addr string) (net.Conn, error),
607 ) *pool.ConnPool {
608 return pool.NewConnPool(&pool.Options{
609 Dialer: func(ctx context.Context) (net.Conn, error) {
610 return dialer(ctx, opt.Network, opt.Addr)
611 },
612 PoolFIFO: opt.PoolFIFO,
613 PoolSize: opt.PoolSize,
614 PoolTimeout: opt.PoolTimeout,
615 DialTimeout: opt.DialTimeout,
616 MinIdleConns: opt.MinIdleConns,
617 MaxIdleConns: opt.MaxIdleConns,
618 MaxActiveConns: opt.MaxActiveConns,
619 ConnMaxIdleTime: opt.ConnMaxIdleTime,
620 ConnMaxLifetime: opt.ConnMaxLifetime,
621 ReadBufferSize: opt.ReadBufferSize,
622 WriteBufferSize: opt.WriteBufferSize,
623 })
624 }
625
View as plain text