diff --git a/bouncer.go b/bouncer.go index 560201f..e32ad47 100644 --- a/bouncer.go +++ b/bouncer.go @@ -12,7 +12,6 @@ import ( "text/template" "time" - "github.com/gomodule/redigo/redis" cache "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/cache" ip "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/ip" logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" @@ -55,7 +54,7 @@ func CreateConfig() *Config { return &Config{ Enabled: false, LogLevel: "INFO", - CrowdsecMode: liveMode, + CrowdsecMode: streamMode, CrowdsecLapiScheme: "http", CrowdsecLapiHost: "crowdsec:8080", CrowdsecLapiKey: "", @@ -125,15 +124,11 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h if config.RedisCacheEnabled { cache.InitRedisClient(config.RedisCacheHost, config.RedisCachePassword) } - if config.CrowdsecMode == streamMode { - go func() { - if ticker == nil { - go handleStreamCache(bouncer) - ticker = startTicker(config, func() { - handleStreamCache(bouncer) - }) - } - }() + if config.CrowdsecMode == streamMode && ticker == nil { + ticker = startTicker(config, func() { + handleStreamCache(bouncer) + }) + go handleStreamCache(bouncer) } return bouncer, nil diff --git a/docker-compose.local.yml b/docker-compose.local.yml index fc8f834..7849d2e 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -35,7 +35,6 @@ services: - "traefik.http.services.service1.loadbalancer.server.port=80" - "traefik.http.middlewares.crowdsec1.plugin.bouncer.enabled=true" - "traefik.http.middlewares.crowdsec1.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.crowdsec1.plugin.bouncer.forwardedheaderstrustedips=172.26.0.5" whoami2: image: traefik/whoami @@ -48,7 +47,6 @@ services: - "traefik.http.services.service2.loadbalancer.server.port=80" - "traefik.http.middlewares.crowdsec2.plugin.bouncer.enabled=true" - "traefik.http.middlewares.crowdsec2.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7" - - "traefik.http.middlewares.crowdsec2.plugin.bouncer.forwardedheaderstrustedips=172.26.0.5" crowdsec: image: crowdsecurity/crowdsec:v1.4.1 diff --git a/exemples/redis-cache/docker-compose.redis.yml b/exemples/redis-cache/docker-compose.redis.yml index a8cf0d2..b179963 100644 --- a/exemples/redis-cache/docker-compose.redis.yml +++ b/exemples/redis-cache/docker-compose.redis.yml @@ -1,4 +1,5 @@ version: "3.8" + services: traefik: image: "traefik:v2.8.8" @@ -22,9 +23,6 @@ services: ports: - 80:80 - 8080:8080 - networks: - - redis - - services depends_on: - crowdsec - redis @@ -35,18 +33,16 @@ services: labels: - "traefik.enable=true" # Definition of the router - - "traefik.http.routers.router1.rule=Path(`/foo`)" + - "traefik.http.routers.router1.rule=Host(`localhost`) && Path(`/foo`)" - "traefik.http.routers.router1.entrypoints=web" - "traefik.http.routers.router1.middlewares=crowdsec1@docker" # Definition of the service - "traefik.http.services.service1.loadbalancer.server.port=80" - # Definitin of the middleware + # Definition of the middleware - "traefik.http.middlewares.crowdsec1.plugin.bouncer.enabled=true" # crowdseclapikey must be uniq to the middleware attached to the service - "traefik.http.middlewares.crowdsec1.plugin.bouncer.crowdseclapikey=40796d93c2958f9e58345514e67740e5" - - "traefik.http.middlewares.crowdsec1.plugin.bouncer.crowdsecmode=live" - networks: - - services + - "traefik.http.middlewares.crowdsec1.plugin.bouncer.rediscacheenabled=true" whoami2: image: traefik/whoami @@ -54,18 +50,16 @@ services: labels: - "traefik.enable=true" # Definition of the router - - "traefik.http.routers.router2.rule=Path(`/bar`)" + - "traefik.http.routers.router2.rule=Host(`localhost`) && Path(`/bar`)" - "traefik.http.routers.router2.entrypoints=web" - "traefik.http.routers.router2.middlewares=crowdsec1@docker" # Definition of the service - "traefik.http.services.service2.loadbalancer.server.port=80" - # Definitin of the middleware + # Definition of the middleware - "traefik.http.middlewares.crowdsec2.plugin.bouncer.enabled=true" # crowdseclapikey must be uniq to the middleware attached to the service - "traefik.http.middlewares.crowdsec2.plugin.bouncer.crowdseclapikey=44c36dac5c4140af9f06f397508e82c7" - - "traefik.http.middlewares.crowdsec2.plugin.bouncer.crowdsecmode=live" - networks: - - services + - "traefik.http.middlewares.crowdsec2.plugin.bouncer.rediscacheenabled=true" crowdsec: @@ -85,20 +79,16 @@ services: - "traefik.enable=false" redis: - image: "redis:7.0.5" + image: "redis:7.0.5-alpine" container_name: "redis" command: "redis-server --save 60 1" volumes: - redis-data:/data - networks: - - redis + ports: + - 6379:6379 volumes: logs-redis: crowdsec-db-redis: crowdsec-config-redis: redis-data: - -networks: - redis: - services: diff --git a/go.mod b/go.mod index d201a02..c3ff139 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,6 @@ module github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin go 1.17 -require ( - github.com/gomodule/redigo v1.8.9 - github.com/leprosus/golang-ttl-map v1.1.7 -) +require github.com/leprosus/golang-ttl-map v1.1.7 + +require github.com/tehnerd/goUtils v0.0.0-20150515130609-5a2d8fb2ded8 // indirect diff --git a/go.sum b/go.sum index 22d3aa8..9a2f881 100644 --- a/go.sum +++ b/go.sum @@ -1,14 +1,4 @@ -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws= -github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE= github.com/leprosus/golang-ttl-map v1.1.7 h1:cF4AAFDDnJTFSV+/42sKLhmMluvLdRlCGS2UaifH6UM= github.com/leprosus/golang-ttl-map v1.1.7/go.mod h1:4QWHJPeVBbrkhOhXdhCv9IEiyj/YzkO04/iexy4vSe0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/tehnerd/goUtils v0.0.0-20150515130609-5a2d8fb2ded8 h1:/b777evAfRRdUJHasZLgQ/w8D/s1HtbaeDXsCVsV0B0= +github.com/tehnerd/goUtils v0.0.0-20150515130609-5a2d8fb2ded8/go.mod h1:UuuqaOb+pZOxJZtjF1mBWTo8HYa7HQCbNkwUEaG9uU0= diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 8a8083f..ea1ccd4 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -1,12 +1,12 @@ package cache import ( - "context" "fmt" ttl_map "github.com/leprosus/golang-ttl-map" logger "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/logger" + simpleredis "github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/pkg/redis" ) const ( @@ -14,21 +14,11 @@ const ( cacheNoBannedValue = "f" ) -var ctx = context.Background() var cache = ttl_map.New() +var redis simpleredis.SimpleRedis var redisEnabled = false -// Get Decision check in the cache if the IP has the banned / not banned value. -// Otherwise return with an error to add the IP in cache if we are on. -func GetDecision(clientIP string) (bool, error) { - if redisEnabled { - return getDecisionRedisCache(clientIP) - } else { - return getDecisionLocalCache(clientIP) - } -} - func getDecisionLocalCache(clientIP string) (bool, error) { banned, isCached := cache.Get(clientIP) bannedString, isValid := banned.(string) @@ -38,14 +28,47 @@ func getDecisionLocalCache(clientIP string) (bool, error) { return false, fmt.Errorf("no cache data") } +func setDecisionLocalCache(clientIP string, value string, duration int64) { + cache.Set(clientIP, value, duration) +} + +func deleteDecisionLocalCache(clientIP string) { + cache.Del(clientIP) +} + func getDecisionRedisCache(clientIP string) (bool, error) { - // banned, err := rdb.Get(ctx, clientIP).Result() - // if err != nil { - // return false, fmt.Errorf("no cache data") - // } else { - // return banned == cacheBannedValue, nil - // } - return false, nil + banned, err := redis.Get(clientIP) + bannedString := string(banned) + if err == nil && len(bannedString) > 0 { + return bannedString == cacheBannedValue, nil + } + return false, fmt.Errorf("no cache data") +} + +func setDecisionRedisCache(clientIP string, value string, duration int64) { + redis.Set(clientIP, []byte(value), duration) +} + +func deleteDecisionRedisCache(clientIP string) { + redis.Del(clientIP) +} + +func DeleteDecision(clientIP string) { + if redisEnabled { + deleteDecisionRedisCache(clientIP) + } else { + deleteDecisionLocalCache(clientIP) + } +} + +// GetDecision check in the cache if the IP has the banned / not banned value. +// Otherwise return with an error to add the IP in cache if we are on. +func GetDecision(clientIP string) (bool, error) { + if redisEnabled { + return getDecisionRedisCache(clientIP) + } else { + return getDecisionLocalCache(clientIP) + } } func SetDecision(clientIP string, isBanned bool, duration int64) { @@ -57,54 +80,14 @@ func SetDecision(clientIP string, isBanned bool, duration int64) { value = cacheNoBannedValue } if redisEnabled { - SetDecisionRedisCache(clientIP, value, duration) + setDecisionRedisCache(clientIP, value, duration) } else { - SetDecisionLocalCache(clientIP, value, duration) + setDecisionLocalCache(clientIP, value, duration) } } -func SetDecisionRedisCache(clientIP string, value string, duration int64) { - // err := rdb.Set(ctx, clientIP, value, time.Duration(duration)).Err() - // if err != nil { - // logger.Info("Error, could not insert in redis cache IP") - // } - // errors are not handled -} - -func SetDecisionLocalCache(clientIP string, value string, duration int64) { - cache.Set(clientIP, value, duration) -} - -func DeleteDecision(clientIP string) { - if redisEnabled { - DeleteDecisionRedisCache(clientIP) - } else { - DeleteDecisionLocalCache(clientIP) - } - -} - -func DeleteDecisionRedisCache(clientIP string) { - // err := rdb.Del(ctx, clientIP).Err() - // if err != nil { - // logger.Info("Error, could not delete in redis cache IP") - // } - // errors are not handled -} - -func DeleteDecisionLocalCache(clientIP string) { - cache.Del(clientIP) -} - -func InitRedisClient(addr string, port int, password string) { - // c, _ := redis.DialContext(ctx, "tcp", ":6379") - // defer c.Close() - // c, err := redis.DialURL(fmt.Sprintf("%v:%d", addr, port)) - // if err != nil && c != nil{ - // // handle error - // // c.Do("Set", "key", "value") - // logger.Debug("test") - // } - // TODO defer c.Close() +func InitRedisClient(host string, password string) { redisEnabled = true + redis.Init(host) + logger.Debug("connect to redis") } diff --git a/pkg/redis/redis.go b/pkg/redis/redis.go new file mode 100644 index 0000000..d073af6 --- /dev/null +++ b/pkg/redis/redis.go @@ -0,0 +1,213 @@ +package simpleredis + +import ( + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/tehnerd/goUtils/netutils" +) + +type RedisCmd struct { + Command string + Name string + Data []byte + Duration int64 + Error error +} + +type SimpleRedis struct { + redisChanRead chan RedisCmd + redisChanWrite chan RedisCmd + redisHost string + redisCmd RedisCmd +} + +func genRedisArray(params ...[]byte) []byte { + MSG := "" + for cntr := 0; cntr < len(params); cntr++ { + MSG = strings.Join([]string{MSG, string(params[cntr])}, " ") + } + MSG = strings.Trim(MSG, " ") + MSG = strings.Join([]string{MSG, "\r\n"}, "") + return []byte(MSG) +} + +func parseResponse(response []byte, dataBuf []byte, Len *int) ([]byte, []byte, error) { + dataBuf = append(dataBuf, response...) + lenCRLF := 2 + if *Len != 0 { + if len(dataBuf) < *Len { + return nil, dataBuf, nil + } else { + return dataBuf[:*Len], dataBuf[*Len:], nil + } + } + for { + switch string(dataBuf[0]) { + case "+", "-", ":": + //simple strings, error,int. usually ther are in format (+|-|:)DATA\r\n" + if len(dataBuf) < 3 { + return nil, dataBuf, nil + } + cntr := 1 + for ; cntr < len(dataBuf); cntr++ { + if dataBuf[cntr] == '\r' { + break + } + } + if cntr == len(dataBuf) { + return nil, dataBuf, nil + } + response = dataBuf[1:cntr] + return response, dataBuf[cntr+2:], nil + case "$": + //bulk string. format $LEN\r\nDATA\r\n. up to 512MB + cntr := 1 + for ; cntr < len(dataBuf); cntr++ { + if string(dataBuf[cntr]) == "\r" { + break + } + } + if cntr == len(dataBuf) || cntr+lenCRLF > len(dataBuf) { + return nil, dataBuf, nil + } + dataLen, err := strconv.Atoi(string(dataBuf[1:cntr])) + if err != nil { + return nil, dataBuf[cntr:], nil + } + + if dataLen == -1 { + return nil, dataBuf[cntr:], fmt.Errorf("NOT FOUND") + } + if cntr+lenCRLF > len(dataBuf)-lenCRLF { + *Len = dataLen + return nil, dataBuf[cntr+lenCRLF:], nil + } + if len(dataBuf[cntr+lenCRLF:len(dataBuf)-lenCRLF]) < dataLen { + *Len = dataLen + return nil, dataBuf[cntr+lenCRLF:], nil + } else { + return dataBuf[cntr+lenCRLF : cntr+dataLen+lenCRLF], dataBuf[cntr+dataLen+lenCRLF:], nil + } + case "*": + panic("array") + default: + if len(dataBuf) > 1 { + dataBuf = dataBuf[1:] + } else { + return nil, dataBuf, nil + } + } + } + return nil, dataBuf, nil +} + +func initContext(hostnamePort string, redisCmdWrite, redisCmdRead chan RedisCmd) { + tcpRemoteAddress, err := net.ResolveTCPAddr("tcp", hostnamePort) + if err != nil { + panic("cant resolve remote redis address") + } + var ladr *net.TCPAddr + msgBuf := make([]byte, 65000) + initMsg := []byte("*1\r\n$4\r\nPING\r\n") + writeChan := make(chan []byte) + readChan := make(chan []byte) + flushChan := make(chan int) + go netutils.AutoRecoonectedTCP(ladr, tcpRemoteAddress, msgBuf, initMsg, writeChan, readChan, flushChan) + <-readChan + dataBuf := make([]byte, 0) + dataLen := 0 + for { + select { + case cmd := <-redisCmdWrite: + switch cmd.Command { + case "SET": + data := genRedisArray([]byte("SET"), []byte(cmd.Name), cmd.Data, []byte("EX"), []byte(fmt.Sprintf("%v", cmd.Duration))) + writeChan <- data + case "GET": + data := genRedisArray([]byte("GET"), []byte(cmd.Name)) + writeChan <- data + case "DEL": + data := genRedisArray([]byte("DEL"), []byte(cmd.Name)) + writeChan <- data + } + case response := <-readChan: + data, dataBuf, err := parseResponse(response, dataBuf, &dataLen) + if dataLen != 0 { + for data == nil { + response = <-readChan + data, dataBuf, err = parseResponse(response, dataBuf, &dataLen) + } + } + if err != nil { + select { + case redisCmdRead <- RedisCmd{ + Error: err, + }: + case <-time.After(time.Second * 5): + } + } + if data != nil && string(data) != "PONG" { + select { + case redisCmdRead <- RedisCmd{ + Data: data, + }: + case <-time.After(time.Second * 5): + } + dataLen = 0 + } + case <-flushChan: + dataBuf = dataBuf[:] + dataLen = 0 + select { + case redisCmdRead <- RedisCmd{}: + case <-time.After(time.Second * 5): + } + } + } +} + +func (sr *SimpleRedis) Init(redisHost string) { + sr.redisHost = redisHost + sr.redisChanWrite = make(chan RedisCmd) + sr.redisChanRead = make(chan RedisCmd) + go initContext(sr.redisHost, sr.redisChanWrite, sr.redisChanRead) +} + +func (sr *SimpleRedis) Get(name string) ([]byte, error) { + sr.redisCmd.Command = "GET" + sr.redisCmd.Name = name + sr.redisChanWrite <- sr.redisCmd + resp := <-sr.redisChanRead + if resp.Error != nil { + return nil, resp.Error + } + return resp.Data, nil +} + +func (sr *SimpleRedis) Set(name string, data []byte, duration int64) error { + sr.redisCmd.Command = "SET" + sr.redisCmd.Name = name + sr.redisCmd.Data = data + sr.redisCmd.Duration = duration + sr.redisChanWrite <- sr.redisCmd + resp := <-sr.redisChanRead + if resp.Error != nil { + return resp.Error + } + return nil +} + +func (sr *SimpleRedis) Del(name string) error { + sr.redisCmd.Command = "DEL" + sr.redisCmd.Name = name + sr.redisChanWrite <- sr.redisCmd + resp := <-sr.redisChanRead + if resp.Error != nil { + return resp.Error + } + return nil +} diff --git a/vendor/github.com/gomodule/redigo/LICENSE b/vendor/github.com/gomodule/redigo/LICENSE deleted file mode 100644 index f433b1a..0000000 --- a/vendor/github.com/gomodule/redigo/LICENSE +++ /dev/null @@ -1,177 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/vendor/github.com/gomodule/redigo/redis/commandinfo.go b/vendor/github.com/gomodule/redigo/redis/commandinfo.go deleted file mode 100644 index b6df6a2..0000000 --- a/vendor/github.com/gomodule/redigo/redis/commandinfo.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2014 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "strings" -) - -const ( - connectionWatchState = 1 << iota - connectionMultiState - connectionSubscribeState - connectionMonitorState -) - -type commandInfo struct { - // Set or Clear these states on connection. - Set, Clear int -} - -var commandInfos = map[string]commandInfo{ - "WATCH": {Set: connectionWatchState}, - "UNWATCH": {Clear: connectionWatchState}, - "MULTI": {Set: connectionMultiState}, - "EXEC": {Clear: connectionWatchState | connectionMultiState}, - "DISCARD": {Clear: connectionWatchState | connectionMultiState}, - "PSUBSCRIBE": {Set: connectionSubscribeState}, - "SUBSCRIBE": {Set: connectionSubscribeState}, - "MONITOR": {Set: connectionMonitorState}, -} - -func init() { - for n, ci := range commandInfos { - commandInfos[strings.ToLower(n)] = ci - } -} - -func lookupCommandInfo(commandName string) commandInfo { - if ci, ok := commandInfos[commandName]; ok { - return ci - } - return commandInfos[strings.ToUpper(commandName)] -} diff --git a/vendor/github.com/gomodule/redigo/redis/conn.go b/vendor/github.com/gomodule/redigo/redis/conn.go deleted file mode 100644 index 753644b..0000000 --- a/vendor/github.com/gomodule/redigo/redis/conn.go +++ /dev/null @@ -1,865 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bufio" - "bytes" - "context" - "crypto/tls" - "errors" - "fmt" - "io" - "net" - "net/url" - "regexp" - "strconv" - "sync" - "time" -) - -var ( - _ ConnWithTimeout = (*conn)(nil) -) - -// conn is the low-level implementation of Conn -type conn struct { - // Shared - mu sync.Mutex - pending int - err error - conn net.Conn - - // Read - readTimeout time.Duration - br *bufio.Reader - - // Write - writeTimeout time.Duration - bw *bufio.Writer - - // Scratch space for formatting argument length. - // '*' or '$', length, "\r\n" - lenScratch [32]byte - - // Scratch space for formatting integers and floats. - numScratch [40]byte -} - -// DialTimeout acts like Dial but takes timeouts for establishing the -// connection to the server, writing a command and reading a reply. -// -// Deprecated: Use Dial with options instead. -func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { - return Dial(network, address, - DialConnectTimeout(connectTimeout), - DialReadTimeout(readTimeout), - DialWriteTimeout(writeTimeout)) -} - -// DialOption specifies an option for dialing a Redis server. -type DialOption struct { - f func(*dialOptions) -} - -type dialOptions struct { - readTimeout time.Duration - writeTimeout time.Duration - tlsHandshakeTimeout time.Duration - dialer *net.Dialer - dialContext func(ctx context.Context, network, addr string) (net.Conn, error) - db int - username string - password string - clientName string - useTLS bool - skipVerify bool - tlsConfig *tls.Config -} - -// DialTLSHandshakeTimeout specifies the maximum amount of time waiting to -// wait for a TLS handshake. Zero means no timeout. -// If no DialTLSHandshakeTimeout option is specified then the default is 30 seconds. -func DialTLSHandshakeTimeout(d time.Duration) DialOption { - return DialOption{func(do *dialOptions) { - do.tlsHandshakeTimeout = d - }} -} - -// DialReadTimeout specifies the timeout for reading a single command reply. -func DialReadTimeout(d time.Duration) DialOption { - return DialOption{func(do *dialOptions) { - do.readTimeout = d - }} -} - -// DialWriteTimeout specifies the timeout for writing a single command. -func DialWriteTimeout(d time.Duration) DialOption { - return DialOption{func(do *dialOptions) { - do.writeTimeout = d - }} -} - -// DialConnectTimeout specifies the timeout for connecting to the Redis server when -// no DialNetDial option is specified. -// If no DialConnectTimeout option is specified then the default is 30 seconds. -func DialConnectTimeout(d time.Duration) DialOption { - return DialOption{func(do *dialOptions) { - do.dialer.Timeout = d - }} -} - -// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server -// when no DialNetDial option is specified. -// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then -// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. -func DialKeepAlive(d time.Duration) DialOption { - return DialOption{func(do *dialOptions) { - do.dialer.KeepAlive = d - }} -} - -// DialNetDial specifies a custom dial function for creating TCP -// connections, otherwise a net.Dialer customized via the other options is used. -// DialNetDial overrides DialConnectTimeout and DialKeepAlive. -func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { - return DialOption{func(do *dialOptions) { - do.dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { - return dial(network, addr) - } - }} -} - -// DialContextFunc specifies a custom dial function with context for creating TCP -// connections, otherwise a net.Dialer customized via the other options is used. -// DialContextFunc overrides DialConnectTimeout and DialKeepAlive. -func DialContextFunc(f func(ctx context.Context, network, addr string) (net.Conn, error)) DialOption { - return DialOption{func(do *dialOptions) { - do.dialContext = f - }} -} - -// DialDatabase specifies the database to select when dialing a connection. -func DialDatabase(db int) DialOption { - return DialOption{func(do *dialOptions) { - do.db = db - }} -} - -// DialPassword specifies the password to use when connecting to -// the Redis server. -func DialPassword(password string) DialOption { - return DialOption{func(do *dialOptions) { - do.password = password - }} -} - -// DialUsername specifies the username to use when connecting to -// the Redis server when Redis ACLs are used. -// A DialPassword must also be passed otherwise this option will have no effect. -func DialUsername(username string) DialOption { - return DialOption{func(do *dialOptions) { - do.username = username - }} -} - -// DialClientName specifies a client name to be used -// by the Redis server connection. -func DialClientName(name string) DialOption { - return DialOption{func(do *dialOptions) { - do.clientName = name - }} -} - -// DialTLSConfig specifies the config to use when a TLS connection is dialed. -// Has no effect when not dialing a TLS connection. -func DialTLSConfig(c *tls.Config) DialOption { - return DialOption{func(do *dialOptions) { - do.tlsConfig = c - }} -} - -// DialTLSSkipVerify disables server name verification when connecting over -// TLS. Has no effect when not dialing a TLS connection. -func DialTLSSkipVerify(skip bool) DialOption { - return DialOption{func(do *dialOptions) { - do.skipVerify = skip - }} -} - -// DialUseTLS specifies whether TLS should be used when connecting to the -// server. This option is ignore by DialURL. -func DialUseTLS(useTLS bool) DialOption { - return DialOption{func(do *dialOptions) { - do.useTLS = useTLS - }} -} - -// Dial connects to the Redis server at the given network and -// address using the specified options. -func Dial(network, address string, options ...DialOption) (Conn, error) { - return DialContext(context.Background(), network, address, options...) -} - -type tlsHandshakeTimeoutError struct{} - -func (tlsHandshakeTimeoutError) Timeout() bool { return true } -func (tlsHandshakeTimeoutError) Temporary() bool { return true } -func (tlsHandshakeTimeoutError) Error() string { return "TLS handshake timeout" } - -// DialContext connects to the Redis server at the given network and -// address using the specified options and context. -func DialContext(ctx context.Context, network, address string, options ...DialOption) (Conn, error) { - do := dialOptions{ - dialer: &net.Dialer{ - Timeout: time.Second * 30, - KeepAlive: time.Minute * 5, - }, - tlsHandshakeTimeout: time.Second * 10, - } - for _, option := range options { - option.f(&do) - } - if do.dialContext == nil { - do.dialContext = do.dialer.DialContext - } - - netConn, err := do.dialContext(ctx, network, address) - if err != nil { - return nil, err - } - - if do.useTLS { - var tlsConfig *tls.Config - if do.tlsConfig == nil { - tlsConfig = &tls.Config{InsecureSkipVerify: do.skipVerify} - } else { - tlsConfig = do.tlsConfig.Clone() - } - if tlsConfig.ServerName == "" { - host, _, err := net.SplitHostPort(address) - if err != nil { - netConn.Close() - return nil, err - } - tlsConfig.ServerName = host - } - - tlsConn := tls.Client(netConn, tlsConfig) - errc := make(chan error, 2) // buffered so we don't block timeout or Handshake - if d := do.tlsHandshakeTimeout; d != 0 { - timer := time.AfterFunc(d, func() { - errc <- tlsHandshakeTimeoutError{} - }) - defer timer.Stop() - } - go func() { - errc <- tlsConn.Handshake() - }() - if err := <-errc; err != nil { - // Timeout or Handshake error. - netConn.Close() // nolint: errcheck - return nil, err - } - - netConn = tlsConn - } - - c := &conn{ - conn: netConn, - bw: bufio.NewWriter(netConn), - br: bufio.NewReader(netConn), - readTimeout: do.readTimeout, - writeTimeout: do.writeTimeout, - } - - if do.password != "" { - authArgs := make([]interface{}, 0, 2) - if do.username != "" { - authArgs = append(authArgs, do.username) - } - authArgs = append(authArgs, do.password) - if _, err := c.DoContext(ctx, "AUTH", authArgs...); err != nil { - netConn.Close() - return nil, err - } - } - - if do.clientName != "" { - if _, err := c.DoContext(ctx, "CLIENT", "SETNAME", do.clientName); err != nil { - netConn.Close() - return nil, err - } - } - - if do.db != 0 { - if _, err := c.DoContext(ctx, "SELECT", do.db); err != nil { - netConn.Close() - return nil, err - } - } - - return c, nil -} - -var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) - -// DialURL wraps DialURLContext using context.Background. -func DialURL(rawurl string, options ...DialOption) (Conn, error) { - ctx := context.Background() - - return DialURLContext(ctx, rawurl, options...) -} - -// DialURLContext connects to a Redis server at the given URL using the Redis -// URI scheme. URLs should follow the draft IANA specification for the -// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). -func DialURLContext(ctx context.Context, rawurl string, options ...DialOption) (Conn, error) { - u, err := url.Parse(rawurl) - if err != nil { - return nil, err - } - - if u.Scheme != "redis" && u.Scheme != "rediss" { - return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) - } - - if u.Opaque != "" { - return nil, fmt.Errorf("invalid redis URL, url is opaque: %s", rawurl) - } - - // As per the IANA draft spec, the host defaults to localhost and - // the port defaults to 6379. - host, port, err := net.SplitHostPort(u.Host) - if err != nil { - // assume port is missing - host = u.Host - port = "6379" - } - if host == "" { - host = "localhost" - } - address := net.JoinHostPort(host, port) - - if u.User != nil { - password, isSet := u.User.Password() - username := u.User.Username() - if isSet { - if username != "" { - // ACL - options = append(options, DialUsername(username), DialPassword(password)) - } else { - // requirepass - user-info username:password with blank username - options = append(options, DialPassword(password)) - } - } else if username != "" { - // requirepass - redis-cli compatibility which treats as single arg in user-info as a password - options = append(options, DialPassword(username)) - } - } - - match := pathDBRegexp.FindStringSubmatch(u.Path) - if len(match) == 2 { - db := 0 - if len(match[1]) > 0 { - db, err = strconv.Atoi(match[1]) - if err != nil { - return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) - } - } - if db != 0 { - options = append(options, DialDatabase(db)) - } - } else if u.Path != "" { - return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) - } - - options = append(options, DialUseTLS(u.Scheme == "rediss")) - - return DialContext(ctx, "tcp", address, options...) -} - -// NewConn returns a new Redigo connection for the given net connection. -func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { - return &conn{ - conn: netConn, - bw: bufio.NewWriter(netConn), - br: bufio.NewReader(netConn), - readTimeout: readTimeout, - writeTimeout: writeTimeout, - } -} - -func (c *conn) Close() error { - c.mu.Lock() - err := c.err - if c.err == nil { - c.err = errors.New("redigo: closed") - err = c.conn.Close() - } - c.mu.Unlock() - return err -} - -func (c *conn) fatal(err error) error { - c.mu.Lock() - if c.err == nil { - c.err = err - // Close connection to force errors on subsequent calls and to unblock - // other reader or writer. - c.conn.Close() - } - c.mu.Unlock() - return err -} - -func (c *conn) Err() error { - c.mu.Lock() - err := c.err - c.mu.Unlock() - return err -} - -func (c *conn) writeLen(prefix byte, n int) error { - c.lenScratch[len(c.lenScratch)-1] = '\n' - c.lenScratch[len(c.lenScratch)-2] = '\r' - i := len(c.lenScratch) - 3 - for { - c.lenScratch[i] = byte('0' + n%10) - i -= 1 - n = n / 10 - if n == 0 { - break - } - } - c.lenScratch[i] = prefix - _, err := c.bw.Write(c.lenScratch[i:]) - return err -} - -func (c *conn) writeString(s string) error { - if err := c.writeLen('$', len(s)); err != nil { - return err - } - if _, err := c.bw.WriteString(s); err != nil { - return err - } - _, err := c.bw.WriteString("\r\n") - return err -} - -func (c *conn) writeBytes(p []byte) error { - if err := c.writeLen('$', len(p)); err != nil { - return err - } - if _, err := c.bw.Write(p); err != nil { - return err - } - _, err := c.bw.WriteString("\r\n") - return err -} - -func (c *conn) writeInt64(n int64) error { - return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) -} - -func (c *conn) writeFloat64(n float64) error { - return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) -} - -func (c *conn) writeCommand(cmd string, args []interface{}) error { - if err := c.writeLen('*', 1+len(args)); err != nil { - return err - } - if err := c.writeString(cmd); err != nil { - return err - } - for _, arg := range args { - if err := c.writeArg(arg, true); err != nil { - return err - } - } - return nil -} - -func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { - switch arg := arg.(type) { - case string: - return c.writeString(arg) - case []byte: - return c.writeBytes(arg) - case int: - return c.writeInt64(int64(arg)) - case int64: - return c.writeInt64(arg) - case float64: - return c.writeFloat64(arg) - case bool: - if arg { - return c.writeString("1") - } else { - return c.writeString("0") - } - case nil: - return c.writeString("") - case Argument: - if argumentTypeOK { - return c.writeArg(arg.RedisArg(), false) - } - // See comment in default clause below. - var buf bytes.Buffer - fmt.Fprint(&buf, arg) - return c.writeBytes(buf.Bytes()) - default: - // This default clause is intended to handle builtin numeric types. - // The function should return an error for other types, but this is not - // done for compatibility with previous versions of the package. - var buf bytes.Buffer - fmt.Fprint(&buf, arg) - return c.writeBytes(buf.Bytes()) - } -} - -type protocolError string - -func (pe protocolError) Error() string { - return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) -} - -// readLine reads a line of input from the RESP stream. -func (c *conn) readLine() ([]byte, error) { - // To avoid allocations, attempt to read the line using ReadSlice. This - // call typically succeeds. The known case where the call fails is when - // reading the output from the MONITOR command. - p, err := c.br.ReadSlice('\n') - if err == bufio.ErrBufferFull { - // The line does not fit in the bufio.Reader's buffer. Fall back to - // allocating a buffer for the line. - buf := append([]byte{}, p...) - for err == bufio.ErrBufferFull { - p, err = c.br.ReadSlice('\n') - buf = append(buf, p...) - } - p = buf - } - if err != nil { - return nil, err - } - i := len(p) - 2 - if i < 0 || p[i] != '\r' { - return nil, protocolError("bad response line terminator") - } - return p[:i], nil -} - -// parseLen parses bulk string and array lengths. -func parseLen(p []byte) (int, error) { - if len(p) == 0 { - return -1, protocolError("malformed length") - } - - if p[0] == '-' && len(p) == 2 && p[1] == '1' { - // handle $-1 and $-1 null replies. - return -1, nil - } - - var n int - for _, b := range p { - n *= 10 - if b < '0' || b > '9' { - return -1, protocolError("illegal bytes in length") - } - n += int(b - '0') - } - - return n, nil -} - -// parseInt parses an integer reply. -func parseInt(p []byte) (interface{}, error) { - if len(p) == 0 { - return 0, protocolError("malformed integer") - } - - var negate bool - if p[0] == '-' { - negate = true - p = p[1:] - if len(p) == 0 { - return 0, protocolError("malformed integer") - } - } - - var n int64 - for _, b := range p { - n *= 10 - if b < '0' || b > '9' { - return 0, protocolError("illegal bytes in length") - } - n += int64(b - '0') - } - - if negate { - n = -n - } - return n, nil -} - -var ( - okReply interface{} = "OK" - pongReply interface{} = "PONG" -) - -func (c *conn) readReply() (interface{}, error) { - line, err := c.readLine() - if err != nil { - return nil, err - } - if len(line) == 0 { - return nil, protocolError("short response line") - } - switch line[0] { - case '+': - switch string(line[1:]) { - case "OK": - // Avoid allocation for frequent "+OK" response. - return okReply, nil - case "PONG": - // Avoid allocation in PING command benchmarks :) - return pongReply, nil - default: - return string(line[1:]), nil - } - case '-': - return Error(line[1:]), nil - case ':': - return parseInt(line[1:]) - case '$': - n, err := parseLen(line[1:]) - if n < 0 || err != nil { - return nil, err - } - p := make([]byte, n) - _, err = io.ReadFull(c.br, p) - if err != nil { - return nil, err - } - if line, err := c.readLine(); err != nil { - return nil, err - } else if len(line) != 0 { - return nil, protocolError("bad bulk string format") - } - return p, nil - case '*': - n, err := parseLen(line[1:]) - if n < 0 || err != nil { - return nil, err - } - r := make([]interface{}, n) - for i := range r { - r[i], err = c.readReply() - if err != nil { - return nil, err - } - } - return r, nil - } - return nil, protocolError("unexpected response line") -} - -func (c *conn) Send(cmd string, args ...interface{}) error { - c.mu.Lock() - c.pending += 1 - c.mu.Unlock() - if c.writeTimeout != 0 { - if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { - return c.fatal(err) - } - } - if err := c.writeCommand(cmd, args); err != nil { - return c.fatal(err) - } - return nil -} - -func (c *conn) Flush() error { - if c.writeTimeout != 0 { - if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { - return c.fatal(err) - } - } - if err := c.bw.Flush(); err != nil { - return c.fatal(err) - } - return nil -} - -func (c *conn) Receive() (interface{}, error) { - return c.ReceiveWithTimeout(c.readTimeout) -} - -func (c *conn) ReceiveContext(ctx context.Context) (interface{}, error) { - var realTimeout time.Duration - if dl, ok := ctx.Deadline(); ok { - timeout := time.Until(dl) - if timeout >= c.readTimeout && c.readTimeout != 0 { - realTimeout = c.readTimeout - } else if timeout <= 0 { - return nil, c.fatal(context.DeadlineExceeded) - } else { - realTimeout = timeout - } - } else { - realTimeout = c.readTimeout - } - endch := make(chan struct{}) - var r interface{} - var e error - go func() { - defer close(endch) - - r, e = c.ReceiveWithTimeout(realTimeout) - }() - select { - case <-ctx.Done(): - return nil, c.fatal(ctx.Err()) - case <-endch: - return r, e - } -} - -func (c *conn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { - var deadline time.Time - if timeout != 0 { - deadline = time.Now().Add(timeout) - } - if err := c.conn.SetReadDeadline(deadline); err != nil { - return nil, c.fatal(err) - } - - if reply, err = c.readReply(); err != nil { - return nil, c.fatal(err) - } - // When using pub/sub, the number of receives can be greater than the - // number of sends. To enable normal use of the connection after - // unsubscribing from all channels, we do not decrement pending to a - // negative value. - // - // The pending field is decremented after the reply is read to handle the - // case where Receive is called before Send. - c.mu.Lock() - if c.pending > 0 { - c.pending -= 1 - } - c.mu.Unlock() - if err, ok := reply.(Error); ok { - return nil, err - } - return -} - -func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { - return c.DoWithTimeout(c.readTimeout, cmd, args...) -} - -func (c *conn) DoContext(ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { - var realTimeout time.Duration - if dl, ok := ctx.Deadline(); ok { - timeout := time.Until(dl) - if timeout >= c.readTimeout && c.readTimeout != 0 { - realTimeout = c.readTimeout - } else if timeout <= 0 { - return nil, c.fatal(context.DeadlineExceeded) - } else { - realTimeout = timeout - } - } else { - realTimeout = c.readTimeout - } - endch := make(chan struct{}) - var r interface{} - var e error - go func() { - defer close(endch) - - r, e = c.DoWithTimeout(realTimeout, cmd, args...) - }() - select { - case <-ctx.Done(): - return nil, c.fatal(ctx.Err()) - case <-endch: - return r, e - } -} - -func (c *conn) DoWithTimeout(readTimeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { - c.mu.Lock() - pending := c.pending - c.pending = 0 - c.mu.Unlock() - - if cmd == "" && pending == 0 { - return nil, nil - } - - if c.writeTimeout != 0 { - if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)); err != nil { - return nil, c.fatal(err) - } - } - - if cmd != "" { - if err := c.writeCommand(cmd, args); err != nil { - return nil, c.fatal(err) - } - } - - if err := c.bw.Flush(); err != nil { - return nil, c.fatal(err) - } - - var deadline time.Time - if readTimeout != 0 { - deadline = time.Now().Add(readTimeout) - } - if err := c.conn.SetReadDeadline(deadline); err != nil { - return nil, c.fatal(err) - } - - if cmd == "" { - reply := make([]interface{}, pending) - for i := range reply { - r, e := c.readReply() - if e != nil { - return nil, c.fatal(e) - } - reply[i] = r - } - return reply, nil - } - - var err error - var reply interface{} - for i := 0; i <= pending; i++ { - var e error - if reply, e = c.readReply(); e != nil { - return nil, c.fatal(e) - } - if e, ok := reply.(Error); ok && err == nil { - err = e - } - } - return reply, err -} diff --git a/vendor/github.com/gomodule/redigo/redis/doc.go b/vendor/github.com/gomodule/redigo/redis/doc.go deleted file mode 100644 index 69ad506..0000000 --- a/vendor/github.com/gomodule/redigo/redis/doc.go +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -// Package redis is a client for the Redis database. -// -// The Redigo FAQ (https://github.com/gomodule/redigo/wiki/FAQ) contains more -// documentation about this package. -// -// Connections -// -// The Conn interface is the primary interface for working with Redis. -// Applications create connections by calling the Dial, DialWithTimeout or -// NewConn functions. In the future, functions will be added for creating -// sharded and other types of connections. -// -// The application must call the connection Close method when the application -// is done with the connection. -// -// Executing Commands -// -// The Conn interface has a generic method for executing Redis commands: -// -// Do(commandName string, args ...interface{}) (reply interface{}, err error) -// -// The Redis command reference (http://redis.io/commands) lists the available -// commands. An example of using the Redis APPEND command is: -// -// n, err := conn.Do("APPEND", "key", "value") -// -// The Do method converts command arguments to bulk strings for transmission -// to the server as follows: -// -// Go Type Conversion -// []byte Sent as is -// string Sent as is -// int, int64 strconv.FormatInt(v) -// float64 strconv.FormatFloat(v, 'g', -1, 64) -// bool true -> "1", false -> "0" -// nil "" -// all other types fmt.Fprint(w, v) -// -// Redis command reply types are represented using the following Go types: -// -// Redis type Go type -// error redis.Error -// integer int64 -// simple string string -// bulk string []byte or nil if value not present. -// array []interface{} or nil if value not present. -// -// Use type assertions or the reply helper functions to convert from -// interface{} to the specific Go type for the command result. -// -// Pipelining -// -// Connections support pipelining using the Send, Flush and Receive methods. -// -// Send(commandName string, args ...interface{}) error -// Flush() error -// Receive() (reply interface{}, err error) -// -// Send writes the command to the connection's output buffer. Flush flushes the -// connection's output buffer to the server. Receive reads a single reply from -// the server. The following example shows a simple pipeline. -// -// c.Send("SET", "foo", "bar") -// c.Send("GET", "foo") -// c.Flush() -// c.Receive() // reply from SET -// v, err = c.Receive() // reply from GET -// -// The Do method combines the functionality of the Send, Flush and Receive -// methods. The Do method starts by writing the command and flushing the output -// buffer. Next, the Do method receives all pending replies including the reply -// for the command just sent by Do. If any of the received replies is an error, -// then Do returns the error. If there are no errors, then Do returns the last -// reply. If the command argument to the Do method is "", then the Do method -// will flush the output buffer and receive pending replies without sending a -// command. -// -// Use the Send and Do methods to implement pipelined transactions. -// -// c.Send("MULTI") -// c.Send("INCR", "foo") -// c.Send("INCR", "bar") -// r, err := c.Do("EXEC") -// fmt.Println(r) // prints [1, 1] -// -// Concurrency -// -// Connections support one concurrent caller to the Receive method and one -// concurrent caller to the Send and Flush methods. No other concurrency is -// supported including concurrent calls to the Do and Close methods. -// -// For full concurrent access to Redis, use the thread-safe Pool to get, use -// and release a connection from within a goroutine. Connections returned from -// a Pool have the concurrency restrictions described in the previous -// paragraph. -// -// Publish and Subscribe -// -// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. -// -// c.Send("SUBSCRIBE", "example") -// c.Flush() -// for { -// reply, err := c.Receive() -// if err != nil { -// return err -// } -// // process pushed message -// } -// -// The PubSubConn type wraps a Conn with convenience methods for implementing -// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods -// send and flush a subscription management command. The receive method -// converts a pushed message to convenient types for use in a type switch. -// -// psc := redis.PubSubConn{Conn: c} -// psc.Subscribe("example") -// for { -// switch v := psc.Receive().(type) { -// case redis.Message: -// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) -// case redis.Subscription: -// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) -// case error: -// return v -// } -// } -// -// Reply Helpers -// -// The Bool, Int, Bytes, String, Strings and Values functions convert a reply -// to a value of a specific type. To allow convenient wrapping of calls to the -// connection Do and Receive methods, the functions take a second argument of -// type error. If the error is non-nil, then the helper function returns the -// error. If the error is nil, the function converts the reply to the specified -// type: -// -// exists, err := redis.Bool(c.Do("EXISTS", "foo")) -// if err != nil { -// // handle error return from c.Do or type conversion error. -// } -// -// The Scan function converts elements of a array reply to Go types: -// -// var value1 int -// var value2 string -// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) -// if err != nil { -// // handle error -// } -// if _, err := redis.Scan(reply, &value1, &value2); err != nil { -// // handle error -// } -// -// Errors -// -// Connection methods return error replies from the server as type redis.Error. -// -// Call the connection Err() method to determine if the connection encountered -// non-recoverable error such as a network error or protocol parsing error. If -// Err() returns a non-nil value, then the connection is not usable and should -// be closed. -package redis diff --git a/vendor/github.com/gomodule/redigo/redis/log.go b/vendor/github.com/gomodule/redigo/redis/log.go deleted file mode 100644 index 72e054f..0000000 --- a/vendor/github.com/gomodule/redigo/redis/log.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bytes" - "context" - "fmt" - "log" - "time" -) - -var ( - _ ConnWithTimeout = (*loggingConn)(nil) -) - -// NewLoggingConn returns a logging wrapper around a connection. -func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { - if prefix != "" { - prefix = prefix + "." - } - return &loggingConn{conn, logger, prefix, nil} -} - -//NewLoggingConnFilter returns a logging wrapper around a connection and a filter function. -func NewLoggingConnFilter(conn Conn, logger *log.Logger, prefix string, skip func(cmdName string) bool) Conn { - if prefix != "" { - prefix = prefix + "." - } - return &loggingConn{conn, logger, prefix, skip} -} - -type loggingConn struct { - Conn - logger *log.Logger - prefix string - skip func(cmdName string) bool -} - -func (c *loggingConn) Close() error { - err := c.Conn.Close() - var buf bytes.Buffer - fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) - c.logger.Output(2, buf.String()) // nolint: errcheck - return err -} - -func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { - const chop = 32 - switch v := v.(type) { - case []byte: - if len(v) > chop { - fmt.Fprintf(buf, "%q...", v[:chop]) - } else { - fmt.Fprintf(buf, "%q", v) - } - case string: - if len(v) > chop { - fmt.Fprintf(buf, "%q...", v[:chop]) - } else { - fmt.Fprintf(buf, "%q", v) - } - case []interface{}: - if len(v) == 0 { - buf.WriteString("[]") - } else { - sep := "[" - fin := "]" - if len(v) > chop { - v = v[:chop] - fin = "...]" - } - for _, vv := range v { - buf.WriteString(sep) - c.printValue(buf, vv) - sep = ", " - } - buf.WriteString(fin) - } - default: - fmt.Fprint(buf, v) - } -} - -func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { - if c.skip != nil && c.skip(commandName) { - return - } - var buf bytes.Buffer - fmt.Fprintf(&buf, "%s%s(", c.prefix, method) - if method != "Receive" { - buf.WriteString(commandName) - for _, arg := range args { - buf.WriteString(", ") - c.printValue(&buf, arg) - } - } - buf.WriteString(") -> (") - if method != "Send" { - c.printValue(&buf, reply) - buf.WriteString(", ") - } - fmt.Fprintf(&buf, "%v)", err) - c.logger.Output(3, buf.String()) // nolint: errcheck -} - -func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { - reply, err := c.Conn.Do(commandName, args...) - c.print("Do", commandName, args, reply, err) - return reply, err -} - -func (c *loggingConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (interface{}, error) { - reply, err := DoContext(c.Conn, ctx, commandName, args...) - c.print("DoContext", commandName, args, reply, err) - return reply, err -} - -func (c *loggingConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (interface{}, error) { - reply, err := DoWithTimeout(c.Conn, timeout, commandName, args...) - c.print("DoWithTimeout", commandName, args, reply, err) - return reply, err -} - -func (c *loggingConn) Send(commandName string, args ...interface{}) error { - err := c.Conn.Send(commandName, args...) - c.print("Send", commandName, args, nil, err) - return err -} - -func (c *loggingConn) Receive() (interface{}, error) { - reply, err := c.Conn.Receive() - c.print("Receive", "", nil, reply, err) - return reply, err -} - -func (c *loggingConn) ReceiveContext(ctx context.Context) (interface{}, error) { - reply, err := ReceiveContext(c.Conn, ctx) - c.print("ReceiveContext", "", nil, reply, err) - return reply, err -} - -func (c *loggingConn) ReceiveWithTimeout(timeout time.Duration) (interface{}, error) { - reply, err := ReceiveWithTimeout(c.Conn, timeout) - c.print("ReceiveWithTimeout", "", nil, reply, err) - return reply, err -} diff --git a/vendor/github.com/gomodule/redigo/redis/pool.go b/vendor/github.com/gomodule/redigo/redis/pool.go deleted file mode 100644 index d7bb71e..0000000 --- a/vendor/github.com/gomodule/redigo/redis/pool.go +++ /dev/null @@ -1,666 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "bytes" - "context" - "crypto/rand" - "crypto/sha1" - "errors" - "io" - "strconv" - "sync" - "time" -) - -var ( - _ ConnWithTimeout = (*activeConn)(nil) - _ ConnWithTimeout = (*errorConn)(nil) -) - -var nowFunc = time.Now // for testing - -// ErrPoolExhausted is returned from a pool connection method (Do, Send, -// Receive, Flush, Err) when the maximum number of database connections in the -// pool has been reached. -var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") - -var ( - errConnClosed = errors.New("redigo: connection closed") -) - -// Pool maintains a pool of connections. The application calls the Get method -// to get a connection from the pool and the connection's Close method to -// return the connection's resources to the pool. -// -// The following example shows how to use a pool in a web application. The -// application creates a pool at application startup and makes it available to -// request handlers using a package level variable. The pool configuration used -// here is an example, not a recommendation. -// -// func newPool(addr string) *redis.Pool { -// return &redis.Pool{ -// MaxIdle: 3, -// IdleTimeout: 240 * time.Second, -// // Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial. -// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, -// } -// } -// -// var ( -// pool *redis.Pool -// redisServer = flag.String("redisServer", ":6379", "") -// ) -// -// func main() { -// flag.Parse() -// pool = newPool(*redisServer) -// ... -// } -// -// A request handler gets a connection from the pool and closes the connection -// when the handler is done: -// -// func serveHome(w http.ResponseWriter, r *http.Request) { -// conn := pool.Get() -// defer conn.Close() -// ... -// } -// -// Use the Dial function to authenticate connections with the AUTH command or -// select a database with the SELECT command: -// -// pool := &redis.Pool{ -// // Other pool configuration not shown in this example. -// Dial: func () (redis.Conn, error) { -// c, err := redis.Dial("tcp", server) -// if err != nil { -// return nil, err -// } -// if _, err := c.Do("AUTH", password); err != nil { -// c.Close() -// return nil, err -// } -// if _, err := c.Do("SELECT", db); err != nil { -// c.Close() -// return nil, err -// } -// return c, nil -// }, -// } -// -// Use the TestOnBorrow function to check the health of an idle connection -// before the connection is returned to the application. This example PINGs -// connections that have been idle more than a minute: -// -// pool := &redis.Pool{ -// // Other pool configuration not shown in this example. -// TestOnBorrow: func(c redis.Conn, t time.Time) error { -// if time.Since(t) < time.Minute { -// return nil -// } -// _, err := c.Do("PING") -// return err -// }, -// } -// -type Pool struct { - // Dial is an application supplied function for creating and configuring a - // connection. - // - // The connection returned from Dial must not be in a special state - // (subscribed to pubsub channel, transaction started, ...). - Dial func() (Conn, error) - - // DialContext is an application supplied function for creating and configuring a - // connection with the given context. - // - // The connection returned from Dial must not be in a special state - // (subscribed to pubsub channel, transaction started, ...). - DialContext func(ctx context.Context) (Conn, error) - - // TestOnBorrow is an optional application supplied function for checking - // the health of an idle connection before the connection is used again by - // the application. Argument t is the time that the connection was returned - // to the pool. If the function returns an error, then the connection is - // closed. - TestOnBorrow func(c Conn, t time.Time) error - - // Maximum number of idle connections in the pool. - MaxIdle int - - // Maximum number of connections allocated by the pool at a given time. - // When zero, there is no limit on the number of connections in the pool. - MaxActive int - - // Close connections after remaining idle for this duration. If the value - // is zero, then idle connections are not closed. Applications should set - // the timeout to a value less than the server's timeout. - IdleTimeout time.Duration - - // If Wait is true and the pool is at the MaxActive limit, then Get() waits - // for a connection to be returned to the pool before returning. - Wait bool - - // Close connections older than this duration. If the value is zero, then - // the pool does not close connections based on age. - MaxConnLifetime time.Duration - - mu sync.Mutex // mu protects the following fields - closed bool // set to true when the pool is closed. - active int // the number of open connections in the pool - initOnce sync.Once // the init ch once func - ch chan struct{} // limits open connections when p.Wait is true - idle idleList // idle connections - waitCount int64 // total number of connections waited for. - waitDuration time.Duration // total time waited for new connections. -} - -// NewPool creates a new pool. -// -// Deprecated: Initialize the Pool directly as shown in the example. -func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { - return &Pool{Dial: newFn, MaxIdle: maxIdle} -} - -// Get gets a connection. The application must close the returned connection. -// This method always returns a valid connection so that applications can defer -// error handling to the first use of the connection. If there is an error -// getting an underlying connection, then the connection Err, Do, Send, Flush -// and Receive methods return that error. -func (p *Pool) Get() Conn { - // GetContext returns errorConn in the first argument when an error occurs. - c, _ := p.GetContext(context.Background()) - return c -} - -// GetContext gets a connection using the provided context. -// -// The provided Context must be non-nil. If the context expires before the -// connection is complete, an error is returned. Any expiration on the context -// will not affect the returned connection. -// -// If the function completes without error, then the application must close the -// returned connection. -func (p *Pool) GetContext(ctx context.Context) (Conn, error) { - // Wait until there is a vacant connection in the pool. - waited, err := p.waitVacantConn(ctx) - if err != nil { - return errorConn{err}, err - } - - p.mu.Lock() - - if waited > 0 { - p.waitCount++ - p.waitDuration += waited - } - - // Prune stale connections at the back of the idle list. - if p.IdleTimeout > 0 { - n := p.idle.count - for i := 0; i < n && p.idle.back != nil && p.idle.back.t.Add(p.IdleTimeout).Before(nowFunc()); i++ { - pc := p.idle.back - p.idle.popBack() - p.mu.Unlock() - pc.c.Close() - p.mu.Lock() - p.active-- - } - } - - // Get idle connection from the front of idle list. - for p.idle.front != nil { - pc := p.idle.front - p.idle.popFront() - p.mu.Unlock() - if (p.TestOnBorrow == nil || p.TestOnBorrow(pc.c, pc.t) == nil) && - (p.MaxConnLifetime == 0 || nowFunc().Sub(pc.created) < p.MaxConnLifetime) { - return &activeConn{p: p, pc: pc}, nil - } - pc.c.Close() - p.mu.Lock() - p.active-- - } - - // Check for pool closed before dialing a new connection. - if p.closed { - p.mu.Unlock() - err := errors.New("redigo: get on closed pool") - return errorConn{err}, err - } - - // Handle limit for p.Wait == false. - if !p.Wait && p.MaxActive > 0 && p.active >= p.MaxActive { - p.mu.Unlock() - return errorConn{ErrPoolExhausted}, ErrPoolExhausted - } - - p.active++ - p.mu.Unlock() - c, err := p.dial(ctx) - if err != nil { - p.mu.Lock() - p.active-- - if p.ch != nil && !p.closed { - p.ch <- struct{}{} - } - p.mu.Unlock() - return errorConn{err}, err - } - return &activeConn{p: p, pc: &poolConn{c: c, created: nowFunc()}}, nil -} - -// PoolStats contains pool statistics. -type PoolStats struct { - // ActiveCount is the number of connections in the pool. The count includes - // idle connections and connections in use. - ActiveCount int - // IdleCount is the number of idle connections in the pool. - IdleCount int - - // WaitCount is the total number of connections waited for. - // This value is currently not guaranteed to be 100% accurate. - WaitCount int64 - - // WaitDuration is the total time blocked waiting for a new connection. - // This value is currently not guaranteed to be 100% accurate. - WaitDuration time.Duration -} - -// Stats returns pool's statistics. -func (p *Pool) Stats() PoolStats { - p.mu.Lock() - stats := PoolStats{ - ActiveCount: p.active, - IdleCount: p.idle.count, - WaitCount: p.waitCount, - WaitDuration: p.waitDuration, - } - p.mu.Unlock() - - return stats -} - -// ActiveCount returns the number of connections in the pool. The count -// includes idle connections and connections in use. -func (p *Pool) ActiveCount() int { - p.mu.Lock() - active := p.active - p.mu.Unlock() - return active -} - -// IdleCount returns the number of idle connections in the pool. -func (p *Pool) IdleCount() int { - p.mu.Lock() - idle := p.idle.count - p.mu.Unlock() - return idle -} - -// Close releases the resources used by the pool. -func (p *Pool) Close() error { - p.mu.Lock() - if p.closed { - p.mu.Unlock() - return nil - } - p.closed = true - p.active -= p.idle.count - pc := p.idle.front - p.idle.count = 0 - p.idle.front, p.idle.back = nil, nil - if p.ch != nil { - close(p.ch) - } - p.mu.Unlock() - for ; pc != nil; pc = pc.next { - pc.c.Close() - } - return nil -} - -func (p *Pool) lazyInit() { - p.initOnce.Do(func() { - p.ch = make(chan struct{}, p.MaxActive) - if p.closed { - close(p.ch) - } else { - for i := 0; i < p.MaxActive; i++ { - p.ch <- struct{}{} - } - } - }) -} - -// waitVacantConn waits for a vacant connection in pool if waiting -// is enabled and pool size is limited, otherwise returns instantly. -// If ctx expires before that, an error is returned. -// -// If there were no vacant connection in the pool right away it returns the time spent waiting -// for that connection to appear in the pool. -func (p *Pool) waitVacantConn(ctx context.Context) (waited time.Duration, err error) { - if !p.Wait || p.MaxActive <= 0 { - // No wait or no connection limit. - return 0, nil - } - - p.lazyInit() - - // wait indicates if we believe it will block so its not 100% accurate - // however for stats it should be good enough. - wait := len(p.ch) == 0 - var start time.Time - if wait { - start = time.Now() - } - - select { - case <-p.ch: - // Additionally check that context hasn't expired while we were waiting, - // because `select` picks a random `case` if several of them are "ready". - select { - case <-ctx.Done(): - p.ch <- struct{}{} - return 0, ctx.Err() - default: - } - case <-ctx.Done(): - return 0, ctx.Err() - } - - if wait { - return time.Since(start), nil - } - return 0, nil -} - -func (p *Pool) dial(ctx context.Context) (Conn, error) { - if p.DialContext != nil { - return p.DialContext(ctx) - } - if p.Dial != nil { - return p.Dial() - } - return nil, errors.New("redigo: must pass Dial or DialContext to pool") -} - -func (p *Pool) put(pc *poolConn, forceClose bool) error { - p.mu.Lock() - if !p.closed && !forceClose { - pc.t = nowFunc() - p.idle.pushFront(pc) - if p.idle.count > p.MaxIdle { - pc = p.idle.back - p.idle.popBack() - } else { - pc = nil - } - } - - if pc != nil { - p.mu.Unlock() - pc.c.Close() - p.mu.Lock() - p.active-- - } - - if p.ch != nil && !p.closed { - p.ch <- struct{}{} - } - p.mu.Unlock() - return nil -} - -type activeConn struct { - p *Pool - pc *poolConn - state int -} - -var ( - sentinel []byte - sentinelOnce sync.Once -) - -func initSentinel() { - p := make([]byte, 64) - if _, err := rand.Read(p); err == nil { - sentinel = p - } else { - h := sha1.New() - io.WriteString(h, "Oops, rand failed. Use time instead.") // nolint: errcheck - io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) // nolint: errcheck - sentinel = h.Sum(nil) - } -} - -func (ac *activeConn) firstError(errs ...error) error { - for _, err := range errs[:len(errs)-1] { - if err != nil { - return err - } - } - return errs[len(errs)-1] -} - -func (ac *activeConn) Close() (err error) { - pc := ac.pc - if pc == nil { - return nil - } - ac.pc = nil - - if ac.state&connectionMultiState != 0 { - err = pc.c.Send("DISCARD") - ac.state &^= (connectionMultiState | connectionWatchState) - } else if ac.state&connectionWatchState != 0 { - err = pc.c.Send("UNWATCH") - ac.state &^= connectionWatchState - } - if ac.state&connectionSubscribeState != 0 { - err = ac.firstError(err, - pc.c.Send("UNSUBSCRIBE"), - pc.c.Send("PUNSUBSCRIBE"), - ) - // To detect the end of the message stream, ask the server to echo - // a sentinel value and read until we see that value. - sentinelOnce.Do(initSentinel) - err = ac.firstError(err, - pc.c.Send("ECHO", sentinel), - pc.c.Flush(), - ) - for { - p, err2 := pc.c.Receive() - if err2 != nil { - err = ac.firstError(err, err2) - break - } - if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { - ac.state &^= connectionSubscribeState - break - } - } - } - _, err2 := pc.c.Do("") - return ac.firstError( - err, - err2, - ac.p.put(pc, ac.state != 0 || pc.c.Err() != nil), - ) -} - -func (ac *activeConn) Err() error { - pc := ac.pc - if pc == nil { - return errConnClosed - } - return pc.c.Err() -} - -func (ac *activeConn) DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - cwt, ok := pc.c.(ConnWithContext) - if !ok { - return nil, errContextNotSupported - } - ci := lookupCommandInfo(commandName) - ac.state = (ac.state | ci.Set) &^ ci.Clear - return cwt.DoContext(ctx, commandName, args...) -} - -func (ac *activeConn) Do(commandName string, args ...interface{}) (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - ci := lookupCommandInfo(commandName) - ac.state = (ac.state | ci.Set) &^ ci.Clear - return pc.c.Do(commandName, args...) -} - -func (ac *activeConn) DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - cwt, ok := pc.c.(ConnWithTimeout) - if !ok { - return nil, errTimeoutNotSupported - } - ci := lookupCommandInfo(commandName) - ac.state = (ac.state | ci.Set) &^ ci.Clear - return cwt.DoWithTimeout(timeout, commandName, args...) -} - -func (ac *activeConn) Send(commandName string, args ...interface{}) error { - pc := ac.pc - if pc == nil { - return errConnClosed - } - ci := lookupCommandInfo(commandName) - ac.state = (ac.state | ci.Set) &^ ci.Clear - return pc.c.Send(commandName, args...) -} - -func (ac *activeConn) Flush() error { - pc := ac.pc - if pc == nil { - return errConnClosed - } - return pc.c.Flush() -} - -func (ac *activeConn) Receive() (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - return pc.c.Receive() -} - -func (ac *activeConn) ReceiveContext(ctx context.Context) (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - cwt, ok := pc.c.(ConnWithContext) - if !ok { - return nil, errContextNotSupported - } - return cwt.ReceiveContext(ctx) -} - -func (ac *activeConn) ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) { - pc := ac.pc - if pc == nil { - return nil, errConnClosed - } - cwt, ok := pc.c.(ConnWithTimeout) - if !ok { - return nil, errTimeoutNotSupported - } - return cwt.ReceiveWithTimeout(timeout) -} - -type errorConn struct{ err error } - -func (ec errorConn) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } -func (ec errorConn) DoContext(context.Context, string, ...interface{}) (interface{}, error) { - return nil, ec.err -} -func (ec errorConn) DoWithTimeout(time.Duration, string, ...interface{}) (interface{}, error) { - return nil, ec.err -} -func (ec errorConn) Send(string, ...interface{}) error { return ec.err } -func (ec errorConn) Err() error { return ec.err } -func (ec errorConn) Close() error { return nil } -func (ec errorConn) Flush() error { return ec.err } -func (ec errorConn) Receive() (interface{}, error) { return nil, ec.err } -func (ec errorConn) ReceiveContext(context.Context) (interface{}, error) { return nil, ec.err } -func (ec errorConn) ReceiveWithTimeout(time.Duration) (interface{}, error) { return nil, ec.err } - -type idleList struct { - count int - front, back *poolConn -} - -type poolConn struct { - c Conn - t time.Time - created time.Time - next, prev *poolConn -} - -func (l *idleList) pushFront(pc *poolConn) { - pc.next = l.front - pc.prev = nil - if l.count == 0 { - l.back = pc - } else { - l.front.prev = pc - } - l.front = pc - l.count++ -} - -func (l *idleList) popFront() { - pc := l.front - l.count-- - if l.count == 0 { - l.front, l.back = nil, nil - } else { - pc.next.prev = nil - l.front = pc.next - } - pc.next, pc.prev = nil, nil -} - -func (l *idleList) popBack() { - pc := l.back - l.count-- - if l.count == 0 { - l.front, l.back = nil, nil - } else { - pc.prev.next = nil - l.back = pc.prev - } - pc.next, pc.prev = nil, nil -} diff --git a/vendor/github.com/gomodule/redigo/redis/pubsub.go b/vendor/github.com/gomodule/redigo/redis/pubsub.go deleted file mode 100644 index 67b885b..0000000 --- a/vendor/github.com/gomodule/redigo/redis/pubsub.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "context" - "errors" - "time" -) - -// Subscription represents a subscribe or unsubscribe notification. -type Subscription struct { - // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" - Kind string - - // The channel that was changed. - Channel string - - // The current number of subscriptions for connection. - Count int -} - -// Message represents a message notification. -type Message struct { - // The originating channel. - Channel string - - // The matched pattern, if any - Pattern string - - // The message data. - Data []byte -} - -// Pong represents a pubsub pong notification. -type Pong struct { - Data string -} - -// PubSubConn wraps a Conn with convenience methods for subscribers. -type PubSubConn struct { - Conn Conn -} - -// Close closes the connection. -func (c PubSubConn) Close() error { - return c.Conn.Close() -} - -// Subscribe subscribes the connection to the specified channels. -func (c PubSubConn) Subscribe(channel ...interface{}) error { - if err := c.Conn.Send("SUBSCRIBE", channel...); err != nil { - return err - } - return c.Conn.Flush() -} - -// PSubscribe subscribes the connection to the given patterns. -func (c PubSubConn) PSubscribe(channel ...interface{}) error { - if err := c.Conn.Send("PSUBSCRIBE", channel...); err != nil { - return err - } - return c.Conn.Flush() -} - -// Unsubscribe unsubscribes the connection from the given channels, or from all -// of them if none is given. -func (c PubSubConn) Unsubscribe(channel ...interface{}) error { - if err := c.Conn.Send("UNSUBSCRIBE", channel...); err != nil { - return err - } - return c.Conn.Flush() -} - -// PUnsubscribe unsubscribes the connection from the given patterns, or from all -// of them if none is given. -func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { - if err := c.Conn.Send("PUNSUBSCRIBE", channel...); err != nil { - return err - } - return c.Conn.Flush() -} - -// Ping sends a PING to the server with the specified data. -// -// The connection must be subscribed to at least one channel or pattern when -// calling this method. -func (c PubSubConn) Ping(data string) error { - if err := c.Conn.Send("PING", data); err != nil { - return err - } - return c.Conn.Flush() -} - -// Receive returns a pushed message as a Subscription, Message, Pong or error. -// The return value is intended to be used directly in a type switch as -// illustrated in the PubSubConn example. -func (c PubSubConn) Receive() interface{} { - return c.receiveInternal(c.Conn.Receive()) -} - -// ReceiveWithTimeout is like Receive, but it allows the application to -// override the connection's default timeout. -func (c PubSubConn) ReceiveWithTimeout(timeout time.Duration) interface{} { - return c.receiveInternal(ReceiveWithTimeout(c.Conn, timeout)) -} - -// ReceiveContext is like Receive, but it allows termination of the receive -// via a Context. If the call returns due to closure of the context's Done -// channel the underlying Conn will have been closed. -func (c PubSubConn) ReceiveContext(ctx context.Context) interface{} { - return c.receiveInternal(ReceiveContext(c.Conn, ctx)) -} - -func (c PubSubConn) receiveInternal(replyArg interface{}, errArg error) interface{} { - reply, err := Values(replyArg, errArg) - if err != nil { - return err - } - - var kind string - reply, err = Scan(reply, &kind) - if err != nil { - return err - } - - switch kind { - case "message": - var m Message - if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { - return err - } - return m - case "pmessage": - var m Message - if _, err := Scan(reply, &m.Pattern, &m.Channel, &m.Data); err != nil { - return err - } - return m - case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": - s := Subscription{Kind: kind} - if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { - return err - } - return s - case "pong": - var p Pong - if _, err := Scan(reply, &p.Data); err != nil { - return err - } - return p - } - return errors.New("redigo: unknown pubsub notification") -} diff --git a/vendor/github.com/gomodule/redigo/redis/redis.go b/vendor/github.com/gomodule/redigo/redis/redis.go deleted file mode 100644 index e3a3968..0000000 --- a/vendor/github.com/gomodule/redigo/redis/redis.go +++ /dev/null @@ -1,213 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "context" - "errors" - "time" -) - -// Error represents an error returned in a command reply. -type Error string - -func (err Error) Error() string { return string(err) } - -// Conn represents a connection to a Redis server. -type Conn interface { - // Close closes the connection. - Close() error - - // Err returns a non-nil value when the connection is not usable. - Err() error - - // Do sends a command to the server and returns the received reply. - // This function will use the timeout which was set when the connection is created - Do(commandName string, args ...interface{}) (reply interface{}, err error) - - // Send writes the command to the client's output buffer. - Send(commandName string, args ...interface{}) error - - // Flush flushes the output buffer to the Redis server. - Flush() error - - // Receive receives a single reply from the Redis server - Receive() (reply interface{}, err error) -} - -// Argument is the interface implemented by an object which wants to control how -// the object is converted to Redis bulk strings. -type Argument interface { - // RedisArg returns a value to be encoded as a bulk string per the - // conversions listed in the section 'Executing Commands'. - // Implementations should typically return a []byte or string. - RedisArg() interface{} -} - -// Scanner is implemented by an object which wants to control its value is -// interpreted when read from Redis. -type Scanner interface { - // RedisScan assigns a value from a Redis value. The argument src is one of - // the reply types listed in the section `Executing Commands`. - // - // An error should be returned if the value cannot be stored without - // loss of information. - RedisScan(src interface{}) error -} - -// ConnWithTimeout is an optional interface that allows the caller to override -// a connection's default read timeout. This interface is useful for executing -// the BLPOP, BRPOP, BRPOPLPUSH, XREAD and other commands that block at the -// server. -// -// A connection's default read timeout is set with the DialReadTimeout dial -// option. Applications should rely on the default timeout for commands that do -// not block at the server. -// -// All of the Conn implementations in this package satisfy the ConnWithTimeout -// interface. -// -// Use the DoWithTimeout and ReceiveWithTimeout helper functions to simplify -// use of this interface. -type ConnWithTimeout interface { - Conn - - // DoWithTimeout sends a command to the server and returns the received reply. - // The timeout overrides the readtimeout set when dialing the connection. - DoWithTimeout(timeout time.Duration, commandName string, args ...interface{}) (reply interface{}, err error) - - // ReceiveWithTimeout receives a single reply from the Redis server. - // The timeout overrides the readtimeout set when dialing the connection. - ReceiveWithTimeout(timeout time.Duration) (reply interface{}, err error) -} - -// ConnWithContext is an optional interface that allows the caller to control the command's life with context. -type ConnWithContext interface { - Conn - - // DoContext sends a command to server and returns the received reply. - // min(ctx,DialReadTimeout()) will be used as the deadline. - // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. - // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). - // ctx timeout return err context.DeadlineExceeded. - // ctx canceled return err context.Canceled. - DoContext(ctx context.Context, commandName string, args ...interface{}) (reply interface{}, err error) - - // ReceiveContext receives a single reply from the Redis server. - // min(ctx,DialReadTimeout()) will be used as the deadline. - // The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. - // DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). - // ctx timeout return err context.DeadlineExceeded. - // ctx canceled return err context.Canceled. - ReceiveContext(ctx context.Context) (reply interface{}, err error) -} - -var errTimeoutNotSupported = errors.New("redis: connection does not support ConnWithTimeout") -var errContextNotSupported = errors.New("redis: connection does not support ConnWithContext") - -// DoContext sends a command to server and returns the received reply. -// min(ctx,DialReadTimeout()) will be used as the deadline. -// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. -// DialReadTimeout() timeout return err can be checked by errors.Is(err, os.ErrDeadlineExceeded). -// ctx timeout return err context.DeadlineExceeded. -// ctx canceled return err context.Canceled. -func DoContext(c Conn, ctx context.Context, cmd string, args ...interface{}) (interface{}, error) { - cwt, ok := c.(ConnWithContext) - if !ok { - return nil, errContextNotSupported - } - return cwt.DoContext(ctx, cmd, args...) -} - -// DoWithTimeout executes a Redis command with the specified read timeout. If -// the connection does not satisfy the ConnWithTimeout interface, then an error -// is returned. -func DoWithTimeout(c Conn, timeout time.Duration, cmd string, args ...interface{}) (interface{}, error) { - cwt, ok := c.(ConnWithTimeout) - if !ok { - return nil, errTimeoutNotSupported - } - return cwt.DoWithTimeout(timeout, cmd, args...) -} - -// ReceiveContext receives a single reply from the Redis server. -// min(ctx,DialReadTimeout()) will be used as the deadline. -// The connection will be closed if DialReadTimeout() timeout or ctx timeout or ctx canceled when this function is running. -// DialReadTimeout() timeout return err can be checked by strings.Contains(e.Error(), "io/timeout"). -// ctx timeout return err context.DeadlineExceeded. -// ctx canceled return err context.Canceled. -func ReceiveContext(c Conn, ctx context.Context) (interface{}, error) { - cwt, ok := c.(ConnWithContext) - if !ok { - return nil, errContextNotSupported - } - return cwt.ReceiveContext(ctx) -} - -// ReceiveWithTimeout receives a reply with the specified read timeout. If the -// connection does not satisfy the ConnWithTimeout interface, then an error is -// returned. -func ReceiveWithTimeout(c Conn, timeout time.Duration) (interface{}, error) { - cwt, ok := c.(ConnWithTimeout) - if !ok { - return nil, errTimeoutNotSupported - } - return cwt.ReceiveWithTimeout(timeout) -} - -// SlowLog represents a redis SlowLog -type SlowLog struct { - // ID is a unique progressive identifier for every slow log entry. - ID int64 - - // Time is the unix timestamp at which the logged command was processed. - Time time.Time - - // ExecutationTime is the amount of time needed for the command execution. - ExecutionTime time.Duration - - // Args is the command name and arguments - Args []string - - // ClientAddr is the client IP address (4.0 only). - ClientAddr string - - // ClientName is the name set via the CLIENT SETNAME command (4.0 only). - ClientName string -} - -// Latency represents a redis LATENCY LATEST. -type Latency struct { - // Name of the latest latency spike event. - Name string - - // Time of the latest latency spike for the event. - Time time.Time - - // Latest is the latest recorded latency for the named event. - Latest time.Duration - - // Max is the maximum latency for the named event. - Max time.Duration -} - -// LatencyHistory represents a redis LATENCY HISTORY. -type LatencyHistory struct { - // Time is the unix timestamp at which the event was processed. - Time time.Time - - // ExecutationTime is the amount of time needed for the command execution. - ExecutionTime time.Duration -} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect.go b/vendor/github.com/gomodule/redigo/redis/reflect.go deleted file mode 100644 index e135aed..0000000 --- a/vendor/github.com/gomodule/redigo/redis/reflect.go +++ /dev/null @@ -1,48 +0,0 @@ -package redis - -import ( - "reflect" - "runtime" -) - -// methodName returns the name of the calling method, -// assumed to be two stack frames above. -func methodName() string { - pc, _, _, _ := runtime.Caller(2) - f := runtime.FuncForPC(pc) - if f == nil { - return "unknown method" - } - return f.Name() -} - -// mustBe panics if f's kind is not expected. -func mustBe(v reflect.Value, expected reflect.Kind) { - if v.Kind() != expected { - panic(&reflect.ValueError{Method: methodName(), Kind: v.Kind()}) - } -} - -// fieldByIndexCreate returns the nested field corresponding -// to index creating elements that are nil when stepping through. -// It panics if v is not a struct. -func fieldByIndexCreate(v reflect.Value, index []int) reflect.Value { - if len(index) == 1 { - return v.Field(index[0]) - } - - mustBe(v, reflect.Struct) - for i, x := range index { - if i > 0 { - if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - } - v = v.Field(x) - } - - return v -} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect_go117.go b/vendor/github.com/gomodule/redigo/redis/reflect_go117.go deleted file mode 100644 index e985192..0000000 --- a/vendor/github.com/gomodule/redigo/redis/reflect_go117.go +++ /dev/null @@ -1,34 +0,0 @@ -//go:build !go1.18 -// +build !go1.18 - -package redis - -import ( - "errors" - "reflect" -) - -// fieldByIndexErr returns the nested field corresponding to index. -// It returns an error if evaluation requires stepping through a nil -// pointer, but panics if it must step through a field that -// is not a struct. -func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { - if len(index) == 1 { - return v.Field(index[0]), nil - } - - mustBe(v, reflect.Struct) - for i, x := range index { - if i > 0 { - if v.Kind() == reflect.Ptr && v.Type().Elem().Kind() == reflect.Struct { - if v.IsNil() { - return reflect.Value{}, errors.New("reflect: indirection through nil pointer to embedded struct field " + v.Type().Elem().Name()) - } - v = v.Elem() - } - } - v = v.Field(x) - } - - return v, nil -} diff --git a/vendor/github.com/gomodule/redigo/redis/reflect_go118.go b/vendor/github.com/gomodule/redigo/redis/reflect_go118.go deleted file mode 100644 index 3356e76..0000000 --- a/vendor/github.com/gomodule/redigo/redis/reflect_go118.go +++ /dev/null @@ -1,16 +0,0 @@ -//go:build go1.18 -// +build go1.18 - -package redis - -import ( - "reflect" -) - -// fieldByIndexErr returns the nested field corresponding to index. -// It returns an error if evaluation requires stepping through a nil -// pointer, but panics if it must step through a field that -// is not a struct. -func fieldByIndexErr(v reflect.Value, index []int) (reflect.Value, error) { - return v.FieldByIndexErr(index) -} diff --git a/vendor/github.com/gomodule/redigo/redis/reply.go b/vendor/github.com/gomodule/redigo/redis/reply.go deleted file mode 100644 index aabf598..0000000 --- a/vendor/github.com/gomodule/redigo/redis/reply.go +++ /dev/null @@ -1,735 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "errors" - "fmt" - "strconv" - "time" -) - -// ErrNil indicates that a reply value is nil. -var ErrNil = errors.New("redigo: nil returned") - -// Int is a helper that converts a command reply to an integer. If err is not -// equal to nil, then Int returns 0, err. Otherwise, Int converts the -// reply to an int as follows: -// -// Reply type Result -// integer int(reply), nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Int(reply interface{}, err error) (int, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - x := int(reply) - if int64(x) != reply { - return 0, strconv.ErrRange - } - return x, nil - case []byte: - n, err := strconv.ParseInt(string(reply), 10, 0) - return int(n), err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) -} - -// Int64 is a helper that converts a command reply to 64 bit integer. If err is -// not equal to nil, then Int64 returns 0, err. Otherwise, Int64 converts the -// reply to an int64 as follows: -// -// Reply type Result -// integer reply, nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Int64(reply interface{}, err error) (int64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - return reply, nil - case []byte: - n, err := strconv.ParseInt(string(reply), 10, 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) -} - -func errNegativeInt(v int64) error { - return fmt.Errorf("redigo: unexpected negative value %v for Uint64", v) -} - -// Uint64 is a helper that converts a command reply to 64 bit unsigned integer. -// If err is not equal to nil, then Uint64 returns 0, err. Otherwise, Uint64 converts the -// reply to an uint64 as follows: -// -// Reply type Result -// +integer reply, nil -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Uint64(reply interface{}, err error) (uint64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case int64: - if reply < 0 { - return 0, errNegativeInt(reply) - } - return uint64(reply), nil - case []byte: - n, err := strconv.ParseUint(string(reply), 10, 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) -} - -// Float64 is a helper that converts a command reply to 64 bit float. If err is -// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts -// the reply to a float64 as follows: -// -// Reply type Result -// bulk string parsed reply, nil -// nil 0, ErrNil -// other 0, error -func Float64(reply interface{}, err error) (float64, error) { - if err != nil { - return 0, err - } - switch reply := reply.(type) { - case []byte: - n, err := strconv.ParseFloat(string(reply), 64) - return n, err - case nil: - return 0, ErrNil - case Error: - return 0, reply - } - return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) -} - -// String is a helper that converts a command reply to a string. If err is not -// equal to nil, then String returns "", err. Otherwise String converts the -// reply to a string as follows: -// -// Reply type Result -// bulk string string(reply), nil -// simple string reply, nil -// nil "", ErrNil -// other "", error -func String(reply interface{}, err error) (string, error) { - if err != nil { - return "", err - } - switch reply := reply.(type) { - case []byte: - return string(reply), nil - case string: - return reply, nil - case nil: - return "", ErrNil - case Error: - return "", reply - } - return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) -} - -// Bytes is a helper that converts a command reply to a slice of bytes. If err -// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts -// the reply to a slice of bytes as follows: -// -// Reply type Result -// bulk string reply, nil -// simple string []byte(reply), nil -// nil nil, ErrNil -// other nil, error -func Bytes(reply interface{}, err error) ([]byte, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []byte: - return reply, nil - case string: - return []byte(reply), nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) -} - -// Bool is a helper that converts a command reply to a boolean. If err is not -// equal to nil, then Bool returns false, err. Otherwise Bool converts the -// reply to boolean as follows: -// -// Reply type Result -// integer value != 0, nil -// bulk string strconv.ParseBool(reply) -// nil false, ErrNil -// other false, error -func Bool(reply interface{}, err error) (bool, error) { - if err != nil { - return false, err - } - switch reply := reply.(type) { - case int64: - return reply != 0, nil - case []byte: - return strconv.ParseBool(string(reply)) - case nil: - return false, ErrNil - case Error: - return false, reply - } - return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) -} - -// MultiBulk is a helper that converts an array command reply to a []interface{}. -// -// Deprecated: Use Values instead. -func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } - -// Values is a helper that converts an array command reply to a []interface{}. -// If err is not equal to nil, then Values returns nil, err. Otherwise, Values -// converts the reply as follows: -// -// Reply type Result -// array reply, nil -// nil nil, ErrNil -// other nil, error -func Values(reply interface{}, err error) ([]interface{}, error) { - if err != nil { - return nil, err - } - switch reply := reply.(type) { - case []interface{}: - return reply, nil - case nil: - return nil, ErrNil - case Error: - return nil, reply - } - return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) -} - -func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { - if err != nil { - return err - } - switch reply := reply.(type) { - case []interface{}: - makeSlice(len(reply)) - for i := range reply { - if reply[i] == nil { - continue - } - if err := assign(i, reply[i]); err != nil { - return err - } - } - return nil - case nil: - return ErrNil - case Error: - return reply - } - return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) -} - -// Float64s is a helper that converts an array command reply to a []float64. If -// err is not equal to nil, then Float64s returns nil, err. Nil array items are -// converted to 0 in the output slice. Floats64 returns an error if an array -// item is not a bulk string or nil. -func Float64s(reply interface{}, err error) ([]float64, error) { - var result []float64 - err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case []byte: - f, err := strconv.ParseFloat(string(v), 64) - result[i] = f - return err - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for Float64s, got type %T", v) - } - }) - return result, err -} - -// Strings is a helper that converts an array command reply to a []string. If -// err is not equal to nil, then Strings returns nil, err. Nil array items are -// converted to "" in the output slice. Strings returns an error if an array -// item is not a bulk string or nil. -func Strings(reply interface{}, err error) ([]string, error) { - var result []string - err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case string: - result[i] = v - return nil - case []byte: - result[i] = string(v) - return nil - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) - } - }) - return result, err -} - -// ByteSlices is a helper that converts an array command reply to a [][]byte. -// If err is not equal to nil, then ByteSlices returns nil, err. Nil array -// items are stay nil. ByteSlices returns an error if an array item is not a -// bulk string or nil. -func ByteSlices(reply interface{}, err error) ([][]byte, error) { - var result [][]byte - err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case []byte: - result[i] = v - return nil - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) - } - }) - return result, err -} - -// Int64s is a helper that converts an array command reply to a []int64. -// If err is not equal to nil, then Int64s returns nil, err. Nil array -// items are stay nil. Int64s returns an error if an array item is not a -// bulk string or nil. -func Int64s(reply interface{}, err error) ([]int64, error) { - var result []int64 - err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case int64: - result[i] = v - return nil - case []byte: - n, err := strconv.ParseInt(string(v), 10, 64) - result[i] = n - return err - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) - } - }) - return result, err -} - -// Ints is a helper that converts an array command reply to a []int. -// If err is not equal to nil, then Ints returns nil, err. Nil array -// items are stay nil. Ints returns an error if an array item is not a -// bulk string or nil. -func Ints(reply interface{}, err error) ([]int, error) { - var result []int - err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case int64: - n := int(v) - if int64(n) != v { - return strconv.ErrRange - } - result[i] = n - return nil - case []byte: - n, err := strconv.Atoi(string(v)) - result[i] = n - return err - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) - } - }) - return result, err -} - -// mapHelper builds a map from the data in reply. -func mapHelper(reply interface{}, err error, name string, makeMap func(int), assign func(key string, value interface{}) error) error { - values, err := Values(reply, err) - if err != nil { - return err - } - - if len(values)%2 != 0 { - return fmt.Errorf("redigo: %s expects even number of values result, got %d", name, len(values)) - } - - makeMap(len(values) / 2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].([]byte) - if !ok { - return fmt.Errorf("redigo: %s key[%d] not a bulk string value, got %T", name, i, values[i]) - } - - if err := assign(string(key), values[i+1]); err != nil { - return err - } - } - - return nil -} - -// StringMap is a helper that converts an array of strings (alternating key, value) -// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. -// Requires an even number of values in result. -func StringMap(reply interface{}, err error) (map[string]string, error) { - var result map[string]string - err = mapHelper(reply, err, "StringMap", - func(n int) { - result = make(map[string]string, n) - }, func(key string, v interface{}) error { - value, ok := v.([]byte) - if !ok { - return fmt.Errorf("redigo: StringMap for %q not a bulk string value, got %T", key, v) - } - - result[key] = string(value) - - return nil - }, - ) - - return result, err -} - -// IntMap is a helper that converts an array of strings (alternating key, value) -// into a map[string]int. The HGETALL commands return replies in this format. -// Requires an even number of values in result. -func IntMap(result interface{}, err error) (map[string]int, error) { - var m map[string]int - err = mapHelper(result, err, "IntMap", - func(n int) { - m = make(map[string]int, n) - }, func(key string, v interface{}) error { - value, err := Int(v, nil) - if err != nil { - return err - } - - m[key] = value - - return nil - }, - ) - - return m, err -} - -// Int64Map is a helper that converts an array of strings (alternating key, value) -// into a map[string]int64. The HGETALL commands return replies in this format. -// Requires an even number of values in result. -func Int64Map(result interface{}, err error) (map[string]int64, error) { - var m map[string]int64 - err = mapHelper(result, err, "Int64Map", - func(n int) { - m = make(map[string]int64, n) - }, func(key string, v interface{}) error { - value, err := Int64(v, nil) - if err != nil { - return err - } - - m[key] = value - - return nil - }, - ) - - return m, err -} - -// Float64Map is a helper that converts an array of strings (alternating key, value) -// into a map[string]float64. The HGETALL commands return replies in this format. -// Requires an even number of values in result. -func Float64Map(result interface{}, err error) (map[string]float64, error) { - var m map[string]float64 - err = mapHelper(result, err, "Float64Map", - func(n int) { - m = make(map[string]float64, n) - }, func(key string, v interface{}) error { - value, err := Float64(v, nil) - if err != nil { - return err - } - - m[key] = value - - return nil - }, - ) - - return m, err -} - -// Positions is a helper that converts an array of positions (lat, long) -// into a [][2]float64. The GEOPOS command returns replies in this format. -func Positions(result interface{}, err error) ([]*[2]float64, error) { - values, err := Values(result, err) - if err != nil { - return nil, err - } - positions := make([]*[2]float64, len(values)) - for i := range values { - if values[i] == nil { - continue - } - - p, ok := values[i].([]interface{}) - if !ok { - return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) - } - - if len(p) != 2 { - return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) - } - - lat, err := Float64(p[0], nil) - if err != nil { - return nil, err - } - - long, err := Float64(p[1], nil) - if err != nil { - return nil, err - } - - positions[i] = &[2]float64{lat, long} - } - return positions, nil -} - -// Uint64s is a helper that converts an array command reply to a []uint64. -// If err is not equal to nil, then Uint64s returns nil, err. Nil array -// items are stay nil. Uint64s returns an error if an array item is not a -// bulk string or nil. -func Uint64s(reply interface{}, err error) ([]uint64, error) { - var result []uint64 - err = sliceHelper(reply, err, "Uint64s", func(n int) { result = make([]uint64, n) }, func(i int, v interface{}) error { - switch v := v.(type) { - case uint64: - result[i] = v - return nil - case []byte: - n, err := strconv.ParseUint(string(v), 10, 64) - result[i] = n - return err - case Error: - return v - default: - return fmt.Errorf("redigo: unexpected element type for Uint64s, got type %T", v) - } - }) - return result, err -} - -// Uint64Map is a helper that converts an array of strings (alternating key, value) -// into a map[string]uint64. The HGETALL commands return replies in this format. -// Requires an even number of values in result. -func Uint64Map(result interface{}, err error) (map[string]uint64, error) { - var m map[string]uint64 - err = mapHelper(result, err, "Uint64Map", - func(n int) { - m = make(map[string]uint64, n) - }, func(key string, v interface{}) error { - value, err := Uint64(v, nil) - if err != nil { - return err - } - - m[key] = value - - return nil - }, - ) - - return m, err -} - -// SlowLogs is a helper that parse the SLOWLOG GET command output and -// return the array of SlowLog -func SlowLogs(result interface{}, err error) ([]SlowLog, error) { - rawLogs, err := Values(result, err) - if err != nil { - return nil, err - } - logs := make([]SlowLog, len(rawLogs)) - for i, e := range rawLogs { - rawLog, ok := e.([]interface{}) - if !ok { - return nil, fmt.Errorf("redigo: slowlog element is not an array, got %T", e) - } - - var log SlowLog - if len(rawLog) < 4 { - return nil, fmt.Errorf("redigo: slowlog element has %d elements, expected at least 4", len(rawLog)) - } - - log.ID, ok = rawLog[0].(int64) - if !ok { - return nil, fmt.Errorf("redigo: slowlog element[0] not an int64, got %T", rawLog[0]) - } - - timestamp, ok := rawLog[1].(int64) - if !ok { - return nil, fmt.Errorf("redigo: slowlog element[1] not an int64, got %T", rawLog[1]) - } - - log.Time = time.Unix(timestamp, 0) - duration, ok := rawLog[2].(int64) - if !ok { - return nil, fmt.Errorf("redigo: slowlog element[2] not an int64, got %T", rawLog[2]) - } - - log.ExecutionTime = time.Duration(duration) * time.Microsecond - - log.Args, err = Strings(rawLog[3], nil) - if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[3] is not array of strings: %w", err) - } - - if len(rawLog) >= 6 { - log.ClientAddr, err = String(rawLog[4], nil) - if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[4] is not a string: %w", err) - } - - log.ClientName, err = String(rawLog[5], nil) - if err != nil { - return nil, fmt.Errorf("redigo: slowlog element[5] is not a string: %w", err) - } - } - logs[i] = log - } - return logs, nil -} - -// Latencies is a helper that parses the LATENCY LATEST command output and -// return the slice of Latency values. -func Latencies(result interface{}, err error) ([]Latency, error) { - rawLatencies, err := Values(result, err) - if err != nil { - return nil, err - } - - latencies := make([]Latency, len(rawLatencies)) - for i, e := range rawLatencies { - rawLatency, ok := e.([]interface{}) - if !ok { - return nil, fmt.Errorf("redigo: latencies element is not slice, got %T", e) - } - - var event Latency - if len(rawLatency) != 4 { - return nil, fmt.Errorf("redigo: latencies element has %d elements, expected 4", len(rawLatency)) - } - - event.Name, err = String(rawLatency[0], nil) - if err != nil { - return nil, fmt.Errorf("redigo: latencies element[0] is not a string: %w", err) - } - - timestamp, ok := rawLatency[1].(int64) - if !ok { - return nil, fmt.Errorf("redigo: latencies element[1] not an int64, got %T", rawLatency[1]) - } - - event.Time = time.Unix(timestamp, 0) - - latestDuration, ok := rawLatency[2].(int64) - if !ok { - return nil, fmt.Errorf("redigo: latencies element[2] not an int64, got %T", rawLatency[2]) - } - - event.Latest = time.Duration(latestDuration) * time.Millisecond - - maxDuration, ok := rawLatency[3].(int64) - if !ok { - return nil, fmt.Errorf("redigo: latencies element[3] not an int64, got %T", rawLatency[3]) - } - - event.Max = time.Duration(maxDuration) * time.Millisecond - - latencies[i] = event - } - - return latencies, nil -} - -// LatencyHistories is a helper that parse the LATENCY HISTORY command output and -// returns a LatencyHistory slice. -func LatencyHistories(result interface{}, err error) ([]LatencyHistory, error) { - rawLogs, err := Values(result, err) - if err != nil { - return nil, err - } - - latencyHistories := make([]LatencyHistory, len(rawLogs)) - for i, e := range rawLogs { - rawLog, ok := e.([]interface{}) - if !ok { - return nil, fmt.Errorf("redigo: latency history element is not an slice, got %T", e) - } - - var event LatencyHistory - timestamp, ok := rawLog[0].(int64) - if !ok { - return nil, fmt.Errorf("redigo: latency history element[0] not an int64, got %T", rawLog[0]) - } - - event.Time = time.Unix(timestamp, 0) - - duration, ok := rawLog[1].(int64) - if !ok { - return nil, fmt.Errorf("redigo: latency history element[1] not an int64, got %T", rawLog[1]) - } - - event.ExecutionTime = time.Duration(duration) * time.Millisecond - - latencyHistories[i] = event - } - - return latencyHistories, nil -} diff --git a/vendor/github.com/gomodule/redigo/redis/scan.go b/vendor/github.com/gomodule/redigo/redis/scan.go deleted file mode 100644 index 8212101..0000000 --- a/vendor/github.com/gomodule/redigo/redis/scan.go +++ /dev/null @@ -1,716 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "errors" - "fmt" - "reflect" - "strconv" - "strings" - "sync" -) - -var ( - scannerType = reflect.TypeOf((*Scanner)(nil)).Elem() -) - -func ensureLen(d reflect.Value, n int) { - if n > d.Cap() { - d.Set(reflect.MakeSlice(d.Type(), n, n)) - } else { - d.SetLen(n) - } -} - -func cannotConvert(d reflect.Value, s interface{}) error { - var sname string - switch s.(type) { - case string: - sname = "Redis simple string" - case Error: - sname = "Redis error" - case int64: - sname = "Redis integer" - case []byte: - sname = "Redis bulk string" - case []interface{}: - sname = "Redis array" - case nil: - sname = "Redis nil" - default: - sname = reflect.TypeOf(s).String() - } - return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) -} - -func convertAssignNil(d reflect.Value) (err error) { - switch d.Type().Kind() { - case reflect.Slice, reflect.Interface: - d.Set(reflect.Zero(d.Type())) - default: - err = cannotConvert(d, nil) - } - return err -} - -func convertAssignError(d reflect.Value, s Error) (err error) { - if d.Kind() == reflect.String { - d.SetString(string(s)) - } else if d.Kind() == reflect.Slice && d.Type().Elem().Kind() == reflect.Uint8 { - d.SetBytes([]byte(s)) - } else { - err = cannotConvert(d, s) - } - return -} - -func convertAssignString(d reflect.Value, s string) (err error) { - switch d.Type().Kind() { - case reflect.Float32, reflect.Float64: - var x float64 - x, err = strconv.ParseFloat(s, d.Type().Bits()) - d.SetFloat(x) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - var x int64 - x, err = strconv.ParseInt(s, 10, d.Type().Bits()) - d.SetInt(x) - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - var x uint64 - x, err = strconv.ParseUint(s, 10, d.Type().Bits()) - d.SetUint(x) - case reflect.Bool: - var x bool - x, err = strconv.ParseBool(s) - d.SetBool(x) - case reflect.String: - d.SetString(s) - case reflect.Slice: - if d.Type().Elem().Kind() == reflect.Uint8 { - d.SetBytes([]byte(s)) - } else { - err = cannotConvert(d, s) - } - case reflect.Ptr: - err = convertAssignString(d.Elem(), s) - default: - err = cannotConvert(d, s) - } - return -} - -func convertAssignBulkString(d reflect.Value, s []byte) (err error) { - switch d.Type().Kind() { - case reflect.Slice: - // Handle []byte destination here to avoid unnecessary - // []byte -> string -> []byte converion. - if d.Type().Elem().Kind() == reflect.Uint8 { - d.SetBytes(s) - } else { - err = cannotConvert(d, s) - } - case reflect.Ptr: - if d.CanInterface() && d.CanSet() { - if s == nil { - if d.IsNil() { - return nil - } - - d.Set(reflect.Zero(d.Type())) - return nil - } - - if d.IsNil() { - d.Set(reflect.New(d.Type().Elem())) - } - - if sc, ok := d.Interface().(Scanner); ok { - return sc.RedisScan(s) - } - } - err = convertAssignString(d, string(s)) - default: - err = convertAssignString(d, string(s)) - } - return err -} - -func convertAssignInt(d reflect.Value, s int64) (err error) { - switch d.Type().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - d.SetInt(s) - if d.Int() != s { - err = strconv.ErrRange - d.SetInt(0) - } - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: - if s < 0 { - err = strconv.ErrRange - } else { - x := uint64(s) - d.SetUint(x) - if d.Uint() != x { - err = strconv.ErrRange - d.SetUint(0) - } - } - case reflect.Bool: - d.SetBool(s != 0) - default: - err = cannotConvert(d, s) - } - return -} - -func convertAssignValue(d reflect.Value, s interface{}) (err error) { - if d.Kind() != reflect.Ptr { - if d.CanAddr() { - d2 := d.Addr() - if d2.CanInterface() { - if scanner, ok := d2.Interface().(Scanner); ok { - return scanner.RedisScan(s) - } - } - } - } else if d.CanInterface() { - // Already a reflect.Ptr - if d.IsNil() { - d.Set(reflect.New(d.Type().Elem())) - } - if scanner, ok := d.Interface().(Scanner); ok { - return scanner.RedisScan(s) - } - } - - switch s := s.(type) { - case nil: - err = convertAssignNil(d) - case []byte: - err = convertAssignBulkString(d, s) - case int64: - err = convertAssignInt(d, s) - case string: - err = convertAssignString(d, s) - case Error: - err = convertAssignError(d, s) - default: - err = cannotConvert(d, s) - } - return err -} - -func convertAssignArray(d reflect.Value, s []interface{}) error { - if d.Type().Kind() != reflect.Slice { - return cannotConvert(d, s) - } - ensureLen(d, len(s)) - for i := 0; i < len(s); i++ { - if err := convertAssignValue(d.Index(i), s[i]); err != nil { - return err - } - } - return nil -} - -func convertAssign(d interface{}, s interface{}) (err error) { - if scanner, ok := d.(Scanner); ok { - return scanner.RedisScan(s) - } - - // Handle the most common destination types using type switches and - // fall back to reflection for all other types. - switch s := s.(type) { - case nil: - // ignore - case []byte: - switch d := d.(type) { - case *string: - *d = string(s) - case *int: - *d, err = strconv.Atoi(string(s)) - case *bool: - *d, err = strconv.ParseBool(string(s)) - case *[]byte: - *d = s - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignBulkString(d.Elem(), s) - } - } - case int64: - switch d := d.(type) { - case *int: - x := int(s) - if int64(x) != s { - err = strconv.ErrRange - x = 0 - } - *d = x - case *bool: - *d = s != 0 - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignInt(d.Elem(), s) - } - } - case string: - switch d := d.(type) { - case *string: - *d = s - case *interface{}: - *d = s - case nil: - // skip value - default: - err = cannotConvert(reflect.ValueOf(d), s) - } - case []interface{}: - switch d := d.(type) { - case *[]interface{}: - *d = s - case *interface{}: - *d = s - case nil: - // skip value - default: - if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { - err = cannotConvert(d, s) - } else { - err = convertAssignArray(d.Elem(), s) - } - } - case Error: - err = s - default: - err = cannotConvert(reflect.ValueOf(d), s) - } - return -} - -// Scan copies from src to the values pointed at by dest. -// -// Scan uses RedisScan if available otherwise: -// -// The values pointed at by dest must be an integer, float, boolean, string, -// []byte, interface{} or slices of these types. Scan uses the standard strconv -// package to convert bulk strings to numeric and boolean types. -// -// If a dest value is nil, then the corresponding src value is skipped. -// -// If a src element is nil, then the corresponding dest value is not modified. -// -// To enable easy use of Scan in a loop, Scan returns the slice of src -// following the copied values. -func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { - if len(src) < len(dest) { - return nil, errors.New("redigo.Scan: array short") - } - var err error - for i, d := range dest { - err = convertAssign(d, src[i]) - if err != nil { - err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) - break - } - } - return src[len(dest):], err -} - -type fieldSpec struct { - name string - index []int - omitEmpty bool -} - -type structSpec struct { - m map[string]*fieldSpec - l []*fieldSpec -} - -func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { - return ss.m[string(name)] -} - -func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec, seen map[reflect.Type]struct{}) error { - if _, ok := seen[t]; ok { - // Protect against infinite recursion. - return fmt.Errorf("recursive struct definition for %v", t) - } - - seen[t] = struct{}{} -LOOP: - for i := 0; i < t.NumField(); i++ { - f := t.Field(i) - switch { - case f.PkgPath != "" && !f.Anonymous: - // Ignore unexported fields. - case f.Anonymous: - switch f.Type.Kind() { - case reflect.Struct: - if err := compileStructSpec(f.Type, depth, append(index, i), ss, seen); err != nil { - return err - } - case reflect.Ptr: - if f.Type.Elem().Kind() == reflect.Struct { - if err := compileStructSpec(f.Type.Elem(), depth, append(index, i), ss, seen); err != nil { - return err - } - } - } - default: - fs := &fieldSpec{name: f.Name} - tag := f.Tag.Get("redis") - - var p string - first := true - for len(tag) > 0 { - i := strings.IndexByte(tag, ',') - if i < 0 { - p, tag = tag, "" - } else { - p, tag = tag[:i], tag[i+1:] - } - if p == "-" { - continue LOOP - } - if first && len(p) > 0 { - fs.name = p - first = false - } else { - switch p { - case "omitempty": - fs.omitEmpty = true - default: - panic(fmt.Errorf("redigo: unknown field tag %s for type %s", p, t.Name())) - } - } - } - - d, found := depth[fs.name] - if !found { - d = 1 << 30 - } - - switch { - case len(index) == d: - // At same depth, remove from result. - delete(ss.m, fs.name) - j := 0 - for i := 0; i < len(ss.l); i++ { - if fs.name != ss.l[i].name { - ss.l[j] = ss.l[i] - j += 1 - } - } - ss.l = ss.l[:j] - case len(index) < d: - fs.index = make([]int, len(index)+1) - copy(fs.index, index) - fs.index[len(index)] = i - depth[fs.name] = len(index) - ss.m[fs.name] = fs - ss.l = append(ss.l, fs) - } - } - } - - return nil -} - -var ( - structSpecMutex sync.RWMutex - structSpecCache = make(map[reflect.Type]*structSpec) -) - -func structSpecForType(t reflect.Type) (*structSpec, error) { - structSpecMutex.RLock() - ss, found := structSpecCache[t] - structSpecMutex.RUnlock() - if found { - return ss, nil - } - - structSpecMutex.Lock() - defer structSpecMutex.Unlock() - ss, found = structSpecCache[t] - if found { - return ss, nil - } - - ss = &structSpec{m: make(map[string]*fieldSpec)} - if err := compileStructSpec(t, make(map[string]int), nil, ss, make(map[reflect.Type]struct{})); err != nil { - return nil, fmt.Errorf("compile struct: %s: %w", t, err) - } - structSpecCache[t] = ss - return ss, nil -} - -var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") - -// ScanStruct scans alternating names and values from src to a struct. The -// HGETALL and CONFIG GET commands return replies in this format. -// -// ScanStruct uses exported field names to match values in the response. Use -// 'redis' field tag to override the name: -// -// Field int `redis:"myName"` -// -// Fields with the tag redis:"-" are ignored. -// -// Each field uses RedisScan if available otherwise: -// Integer, float, boolean, string and []byte fields are supported. Scan uses the -// standard strconv package to convert bulk string values to numeric and -// boolean types. -// -// If a src element is nil, then the corresponding field is not modified. -func ScanStruct(src []interface{}, dest interface{}) error { - d := reflect.ValueOf(dest) - if d.Kind() != reflect.Ptr || d.IsNil() { - return errScanStructValue - } - - d = d.Elem() - if d.Kind() != reflect.Struct { - return errScanStructValue - } - - if len(src)%2 != 0 { - return errors.New("redigo.ScanStruct: number of values not a multiple of 2") - } - - ss, err := structSpecForType(d.Type()) - if err != nil { - return fmt.Errorf("redigo.ScanStruct: %w", err) - } - - for i := 0; i < len(src); i += 2 { - s := src[i+1] - if s == nil { - continue - } - - name, ok := src[i].([]byte) - if !ok { - return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) - } - - fs := ss.fieldSpec(name) - if fs == nil { - continue - } - - if err := convertAssignValue(fieldByIndexCreate(d, fs.index), s); err != nil { - return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) - } - } - return nil -} - -var ( - errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") -) - -// ScanSlice scans src to the slice pointed to by dest. -// -// If the target is a slice of types which implement Scanner then the custom -// RedisScan method is used otherwise the following rules apply: -// -// The elements in the dest slice must be integer, float, boolean, string, struct -// or pointer to struct values. -// -// Struct fields must be integer, float, boolean or string values. All struct -// fields are used unless a subset is specified using fieldNames. -func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { - d := reflect.ValueOf(dest) - if d.Kind() != reflect.Ptr || d.IsNil() { - return errScanSliceValue - } - d = d.Elem() - if d.Kind() != reflect.Slice { - return errScanSliceValue - } - - isPtr := false - t := d.Type().Elem() - st := t - if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { - isPtr = true - t = t.Elem() - } - - if t.Kind() != reflect.Struct || st.Implements(scannerType) { - ensureLen(d, len(src)) - for i, s := range src { - if s == nil { - continue - } - if err := convertAssignValue(d.Index(i), s); err != nil { - return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) - } - } - return nil - } - - ss, err := structSpecForType(t) - if err != nil { - return fmt.Errorf("redigo.ScanSlice: %w", err) - } - - fss := ss.l - if len(fieldNames) > 0 { - fss = make([]*fieldSpec, len(fieldNames)) - for i, name := range fieldNames { - fss[i] = ss.m[name] - if fss[i] == nil { - return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) - } - } - } - - if len(fss) == 0 { - return errors.New("redigo.ScanSlice: no struct fields") - } - - n := len(src) / len(fss) - if n*len(fss) != len(src) { - return errors.New("redigo.ScanSlice: length not a multiple of struct field count") - } - - ensureLen(d, n) - for i := 0; i < n; i++ { - d := d.Index(i) - if isPtr { - if d.IsNil() { - d.Set(reflect.New(t)) - } - d = d.Elem() - } - for j, fs := range fss { - s := src[i*len(fss)+j] - if s == nil { - continue - } - if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { - return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) - } - } - } - return nil -} - -// Args is a helper for constructing command arguments from structured values. -type Args []interface{} - -// Add returns the result of appending value to args. -func (args Args) Add(value ...interface{}) Args { - return append(args, value...) -} - -// AddFlat returns the result of appending the flattened value of v to args. -// -// Maps are flattened by appending the alternating keys and map values to args. -// -// Slices are flattened by appending the slice elements to args. -// -// Structs are flattened by appending the alternating names and values of -// exported fields to args. If v is a nil struct pointer, then nothing is -// appended. The 'redis' field tag overrides struct field names. See ScanStruct -// for more information on the use of the 'redis' field tag. -// -// Other types are appended to args as is. -// panics if v includes a recursive anonymous struct. -func (args Args) AddFlat(v interface{}) Args { - rv := reflect.ValueOf(v) - switch rv.Kind() { - case reflect.Struct: - args = flattenStruct(args, rv) - case reflect.Slice: - for i := 0; i < rv.Len(); i++ { - args = append(args, rv.Index(i).Interface()) - } - case reflect.Map: - for _, k := range rv.MapKeys() { - args = append(args, k.Interface(), rv.MapIndex(k).Interface()) - } - case reflect.Ptr: - if rv.Type().Elem().Kind() == reflect.Struct { - if !rv.IsNil() { - args = flattenStruct(args, rv.Elem()) - } - } else { - args = append(args, v) - } - default: - args = append(args, v) - } - return args -} - -func flattenStruct(args Args, v reflect.Value) Args { - ss, err := structSpecForType(v.Type()) - if err != nil { - panic(fmt.Errorf("redigo.AddFlat: %w", err)) - } - - for _, fs := range ss.l { - fv, err := fieldByIndexErr(v, fs.index) - if err != nil { - // Nil item ignore. - continue - } - if fs.omitEmpty { - var empty = false - switch fv.Kind() { - case reflect.Array, reflect.Map, reflect.Slice, reflect.String: - empty = fv.Len() == 0 - case reflect.Bool: - empty = !fv.Bool() - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - empty = fv.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - empty = fv.Uint() == 0 - case reflect.Float32, reflect.Float64: - empty = fv.Float() == 0 - case reflect.Interface, reflect.Ptr: - empty = fv.IsNil() - } - if empty { - continue - } - } - if arg, ok := fv.Interface().(Argument); ok { - args = append(args, fs.name, arg.RedisArg()) - } else if fv.Kind() == reflect.Ptr { - if !fv.IsNil() { - args = append(args, fs.name, fv.Elem().Interface()) - } - } else { - args = append(args, fs.name, fv.Interface()) - } - } - return args -} diff --git a/vendor/github.com/gomodule/redigo/redis/script.go b/vendor/github.com/gomodule/redigo/redis/script.go deleted file mode 100644 index c585518..0000000 --- a/vendor/github.com/gomodule/redigo/redis/script.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2012 Gary Burd -// -// Licensed under the Apache License, Version 2.0 (the "License"): you may -// not use this file except in compliance with the License. You may obtain -// a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -// License for the specific language governing permissions and limitations -// under the License. - -package redis - -import ( - "context" - "crypto/sha1" - "encoding/hex" - "io" - "strings" -) - -// Script encapsulates the source, hash and key count for a Lua script. See -// http://redis.io/commands/eval for information on scripts in Redis. -type Script struct { - keyCount int - src string - hash string -} - -// NewScript returns a new script object. If keyCount is greater than or equal -// to zero, then the count is automatically inserted in the EVAL command -// argument list. If keyCount is less than zero, then the application supplies -// the count as the first value in the keysAndArgs argument to the Do, Send and -// SendHash methods. -func NewScript(keyCount int, src string) *Script { - h := sha1.New() - io.WriteString(h, src) // nolint: errcheck - return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} -} - -func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { - var args []interface{} - if s.keyCount < 0 { - args = make([]interface{}, 1+len(keysAndArgs)) - args[0] = spec - copy(args[1:], keysAndArgs) - } else { - args = make([]interface{}, 2+len(keysAndArgs)) - args[0] = spec - args[1] = s.keyCount - copy(args[2:], keysAndArgs) - } - return args -} - -// Hash returns the script hash. -func (s *Script) Hash() string { - return s.hash -} - -func (s *Script) DoContext(ctx context.Context, c Conn, keysAndArgs ...interface{}) (interface{}, error) { - cwt, ok := c.(ConnWithContext) - if !ok { - return nil, errContextNotSupported - } - v, err := cwt.DoContext(ctx, "EVALSHA", s.args(s.hash, keysAndArgs)...) - if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { - v, err = cwt.DoContext(ctx, "EVAL", s.args(s.src, keysAndArgs)...) - } - return v, err -} - -// Do evaluates the script. Under the covers, Do optimistically evaluates the -// script using the EVALSHA command. If the command fails because the script is -// not loaded, then Do evaluates the script using the EVAL command (thus -// causing the script to load). -func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { - v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) - if err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") { - v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) - } - return v, err -} - -// SendHash evaluates the script without waiting for the reply. The script is -// evaluated with the EVALSHA command. The application must ensure that the -// script is loaded by a previous call to Send, Do or Load methods. -func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { - return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) -} - -// Send evaluates the script without waiting for the reply. -func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { - return c.Send("EVAL", s.args(s.src, keysAndArgs)...) -} - -// Load loads the script without evaluating it. -func (s *Script) Load(c Conn) error { - _, err := c.Do("SCRIPT", "LOAD", s.src) - return err -} diff --git a/vendor/github.com/tehnerd/goUtils/netutils/netutils.go b/vendor/github.com/tehnerd/goUtils/netutils/netutils.go new file mode 100644 index 0000000..ddfea12 --- /dev/null +++ b/vendor/github.com/tehnerd/goUtils/netutils/netutils.go @@ -0,0 +1,525 @@ +package netutils + +import ( + "math/rand" + "net" + "strconv" + "strings" + "sync" + //"sync/atomic" + "time" +) + +//Receive msg from tcp socket and send it as a []byte to readChan +func ReadFromTCP2(sock *net.TCPConn, msgBuf []byte, readChan chan []byte, + feedbackChanFromSocket chan int) { + loop := 1 + for loop == 1 { + bytes, err := sock.Read(msgBuf) + if err != nil { + feedbackChanFromSocket <- 1 + loop = 0 + continue + } + b := make([]byte, 0) + b = append(b, msgBuf[:bytes]...) + readChan <- b + } +} + +//Receive msg from tcp socket and send it as a []byte to readChan +func ReadFromTCP(sock *net.TCPConn, msgBuf []byte, readChan chan []byte, + feedbackChanFromSocket chan int) { + feedbackToSocket := make(chan bool) + feedbackFromSocket := make(chan bool) + reuseBufferChan := make(chan []byte, 1) + go readFromTCP(sock, readChan, feedbackFromSocket, + feedbackToSocket, reuseBufferChan) + go func() { + <-feedbackFromSocket + feedbackChanFromSocket <- 1 + }() +} + +/*simple write to TCP, for oneway connections only (no communitcation w/ "read" part of the socket +in terms of error propogation)*/ +func WriteToTCPw2(sock *net.TCPConn, writeChan chan []byte, + feedbackChan chan int) { + loop := 1 + for loop == 1 { + select { + case msg := <-writeChan: + _, err := sock.Write(msg) + if err != nil { + feedbackChan <- 1 + continue + } + case <-feedbackChan: + loop = 0 + } + } +} + +/*simple write to TCP, for oneway connections only (no communitcation w/ "read" part of the socket +in terms of error propogation)*/ +func WriteToTCPw(sock *net.TCPConn, writeChan chan []byte, + feedbackChan chan int) { + feedbackToSocket := make(chan bool) + feedbackFromSocket := make(chan bool) + go writeToTCP(sock, writeChan, feedbackFromSocket, feedbackToSocket) + go func() { + <-feedbackFromSocket + feedbackChan <- 1 + }() +} + +//simple write to tcp w/ erorr propagation to/from "read" part of the socket +func WriteToTCPrw2(sock *net.TCPConn, writeChan chan []byte, + feedbackChanFromSocket, feedbackChanToSocket chan int) { + loop := 1 + for loop == 1 { + select { + case msg := <-writeChan: + _, err := sock.Write(msg) + if err != nil { + select { + case feedbackChanFromSocket <- 1: + continue + case loop = <-feedbackChanToSocket: + loop = 0 + continue + } + } + case <-feedbackChanToSocket: + loop = 0 + } + } +} + +//simple write to tcp w/ erorr propagation to/from "read" part of the socket +func WriteToTCPrw(sock *net.TCPConn, writeChan chan []byte, + feedbackChanFromSocket, feedbackChanToSocket chan int) { + feedbackToSocket := make(chan bool) + feedbackFromSocket := make(chan bool) + go writeToTCP(sock, writeChan, feedbackFromSocket, feedbackToSocket) + go func() { + select { + case <-feedbackFromSocket: + feedbackChanFromSocket <- 1 + case <-feedbackChanToSocket: + feedbackToSocket <- true + + } + }() + +} + +func makeBuffer(reuseBufferChan chan []byte) []byte { + select { + case buf := <-reuseBufferChan: + return buf + default: + return make([]byte, 10000) + } +} + +//Receive msg from tcp socket and send it as a []byte to readChan,with buffer reuse +func readFromTCP(sock *net.TCPConn, readChan chan []byte, + feedbackFromSocket, feedbackToSocket chan bool, + reuseBufferChan chan []byte) { + loop := 1 + var buf []byte + for loop == 1 { + buf = makeBuffer(reuseBufferChan) + bytes, err := sock.Read(buf) + if err != nil { + select { + case feedbackFromSocket <- true: + loop = 0 + continue + case <-feedbackToSocket: + loop = 0 + continue + } + } + select { + case readChan <- buf[:bytes]: + case <-feedbackToSocket: + loop = 0 + continue + + } + } +} + +//TCP's write routine, with feedback's chans +func writeToTCP(sock *net.TCPConn, writeChan chan []byte, + feedbackFromSocket, feedbackToSocket chan bool) { + loop := 1 + for loop == 1 { + select { + case msg := <-writeChan: + _, err := sock.Write(msg) + if err != nil { + select { + case feedbackFromSocket <- true: + case <-feedbackToSocket: + } + loop = 0 + continue + } + case <-feedbackToSocket: + loop = 0 + continue + } + } +} + +//reconnecting to remote host for both read and write purpose +func ReconnectTCPRW(ladr, radr *net.TCPAddr, msgBuf []byte, writeChan chan []byte, + readChan chan []byte, feedbackChanToSocket, feedbackChanFromSocket chan int, + init_msg []byte) { + loop := 1 + for loop == 1 { + sock, err := net.DialTCP("tcp", ladr, radr) + if err != nil { + time.Sleep(time.Duration(20+rand.Intn(15)) * time.Second) + continue + } + //testing health of the new socket. GO sometimes doesnt rise the error when + // we receive RST from remote side + _, err = sock.Write(init_msg) + if err != nil { + sock.Close() + time.Sleep(time.Duration(20+rand.Intn(15)) * time.Second) + continue + } + loop = 0 + go ReadFromTCP(sock, msgBuf, readChan, feedbackChanFromSocket) + go WriteToTCPrw(sock, writeChan, feedbackChanFromSocket, feedbackChanToSocket) + } +} + +func ReconnectTCPRWReuse(ladr, radr *net.TCPAddr, + readChan, writeChan, reuseBufferChan chan []byte, + readFeedbackFrom, readFeedbackTo chan bool, + writeFeedbackFrom, writeFeedbackTo chan bool) { + loop := 1 + for loop == 1 { + sock, err := net.DialTCP("tcp", ladr, radr) + if err != nil { + time.Sleep(time.Duration(20+rand.Intn(15)) * time.Second) + continue + } + loop = 0 + go readFromTCP(sock, readChan, readFeedbackFrom, readFeedbackTo, + reuseBufferChan) + go writeToTCP(sock, writeChan, writeFeedbackFrom, writeFeedbackTo) + } +} + +func AutoRecoonectedTCP(ladr, radr *net.TCPAddr, msgBuf, initMsg []byte, + writeChan, readChan chan []byte, flushChan chan int) { + feedbackChanFromSocket := make(chan int) + feedbackChanToSocket := make(chan int) + go ReconnectTCPRW(ladr, radr, msgBuf, writeChan, readChan, feedbackChanToSocket, + feedbackChanFromSocket, initMsg) + for { + select { + case feedbackFromSocket := <-feedbackChanFromSocket: + feedbackChanToSocket <- feedbackFromSocket + flushChan <- 1 + go ReconnectTCPRW(ladr, radr, msgBuf, writeChan, + readChan, feedbackChanToSocket, + feedbackChanFromSocket, initMsg) + + } + } + +} + +func AutoRecoonectedTCPReuse(ladr, radr *net.TCPAddr, + readChan, writeChan chan []byte, + reuseChan chan []byte, + flushChan chan bool) { + readFeedbackFrom := make(chan bool) + readFeedbackTo := make(chan bool) + writeFeedbackFrom := make(chan bool) + writeFeedbackTo := make(chan bool) + go ReconnectTCPRWReuse(ladr, radr, readChan, writeChan, reuseChan, + readFeedbackFrom, readFeedbackTo, + writeFeedbackFrom, writeFeedbackTo) + for { + select { + case <-readFeedbackFrom: + writeFeedbackTo <- true + case <-writeFeedbackFrom: + readFeedbackTo <- true + } + flushChan <- true + go ReconnectTCPRWReuse(ladr, radr, readChan, writeChan, reuseChan, + readFeedbackFrom, readFeedbackTo, + writeFeedbackFrom, writeFeedbackTo) + } + +} + +//reconnecting to remote host for write only +func ReconnectTCPW(radr net.TCPAddr, writeChan chan []byte, feedbackChan chan int) { + loop := 1 + for loop == 1 { + time.Sleep(time.Duration(20+rand.Intn(15)) * time.Second) + sock, err := net.DialTCP("tcp", nil, &radr) + if err != nil { + continue + } + //testing health of the new socket. GO sometimes doesnt rise the error when + // we receive RST from remote side + _, err = sock.Write([]byte{1}) + if err != nil { + sock.Close() + continue + } + loop = 0 + go WriteToTCPw(sock, writeChan, feedbackChan) + } +} + +/* --------------------- CONNECTION MANAGER ------------------------- +Connection manager will allow send data and receive data from remote hosts. +it will have single ConnectionMsg (see below) read chan and single +ConnectionMsg write chan toward it's clients +as well as single read chan from sockets, but multiple write sockets. +it will route msgs according to Host field in connectionManager struct +(if it received from client, it will send this msg toward Host's sockets; +if recved from socket, will proxy it toward client(and client will know from which remote host +it was received) +-------------------------------------------------------------------- */ +/* +MsgType's could be: +from Api's client to ConnectionManager: +"Data" - msg with Data to Host +"Connect" - connect to new Host +... +from ConnectionManager to Api's client: +"BufferFlush" - notification that connection to remote Host not longer working. +advice to flush all the msg buffers assosiated with remote host +*/ +type ConnectionMsg struct { + Host string + Data []byte + Type string +} + +/* +Receive msg from tcp socket and send it as a ConnectionMsg to readChan +TODO: think about more generic version to be more DRYer (to work in both CM and []byte chans +*/ +func CMReadFromTCP(sock *net.TCPConn, readChan chan ConnectionMsg, + peerAddress string) { + msgBuf := make([]byte, 65000) + loop := 1 + var msg ConnectionMsg + msg.Host = peerAddress + msg.Type = "Data" + for loop == 1 { + bytes, err := sock.Read(msgBuf) + if err != nil { + msg.Type = "ReadError" + readChan <- msg + loop = 0 + continue + } + b := make([]byte, 0) + b = append(b, msgBuf[:bytes]...) + msg.Data = b + readChan <- msg + } +} + +/* +ConnectionManager write instance to tcp w/ erorr propagation to/from "read" part of the socket +TODO: think about more generic version to be more DRYer (to work in both CM and []byte chans +*/ +func CMWriteToTCP(sock *net.TCPConn, writeChan, readChan chan ConnectionMsg, + peerAddress string) { + loop := 1 + var errorMsg ConnectionMsg + errorMsg.Host = peerAddress + errorMsg.Type = "WriteError" + for loop == 1 { + select { + case msg := <-writeChan: + switch msg.Type { + case "Data": + _, err := sock.Write(msg.Data) + if err != nil { + for loop == 1 { + select { + case readChan <- errorMsg: + case errorMsg := <-writeChan: + if errorMsg.Type != "ConnectionError" { + continue + } + } + loop = 0 + } + } + case "ConnectionError": + loop = 0 + default: + continue + } + } + } +} + +func StartConnection(tcpConn *net.TCPConn, writeChan, + readChan chan ConnectionMsg, peerAddress string) { + go CMReadFromTCP(tcpConn, readChan, peerAddress) + go CMWriteToTCP(tcpConn, writeChan, readChan, peerAddress) +} + +func CMListenForConnection(mutex *sync.RWMutex, localPort int, + writeChanMap map[string]chan ConnectionMsg, + connectionStateMap map[string]int, + readChan chan ConnectionMsg) { + laddr := strings.Join([]string{":", strconv.Itoa(localPort)}, "") + tcpLaddr, err := net.ResolveTCPAddr("tcp", laddr) + if err != nil { + panic("cant resolve local address for binding") + } + tcpListener, err := net.ListenTCP("tcp", tcpLaddr) + if err != nil { + panic("cant listen on local address for binding") + } + for { + tcpConn, err := tcpListener.AcceptTCP() + if err == nil { + radr := strings.Split(tcpConn.RemoteAddr().String(), ":")[0] + // check if we already has connection to remote peer as a client + mutex.Lock() + if val, exist := connectionStateMap[radr]; exist && val == 1 { + tcpConn.Close() + mutex.Unlock() + continue + } + connectionStateMap[radr] = 1 + if writeChan, exist := writeChanMap[radr]; exist { + mutex.Unlock() + go StartConnection(tcpConn, writeChan, readChan, radr) + } else { + writeChanMap[radr] = make(chan ConnectionMsg) + mutex.Unlock() + go StartConnection(tcpConn, writeChanMap[radr], readChan, radr) + } + } + } +} + +func CMConnectToRemotePeer(mutex *sync.RWMutex, peerTcpAddr *net.TCPAddr, + radr string, + writeChan chan ConnectionMsg, + readChan chan ConnectionMsg, + connectionStateMap map[string]int) { + connectLoop := 1 + for connectLoop == 1 { + mutex.RLock() + if connectionStateMap[radr] == 1 { + connectLoop = 0 + mutex.RUnlock() + continue + } + mutex.RUnlock() + tcpConn, err := net.DialTCP("tcp", nil, peerTcpAddr) + if err != nil { + time.Sleep(time.Second * time.Duration(rand.Int63n(15))) + continue + } + mutex.Lock() + if connectionStateMap[radr] == 0 { + connectionStateMap[radr] = 1 + go StartConnection(tcpConn, writeChan, readChan, radr) + mutex.Unlock() + connectLoop = 0 + continue + } else { + mutex.Unlock() + tcpConn.Close() + connectLoop = 0 + continue + } + } +} + +func ConnectionManager(msgChan chan ConnectionMsg, localPort int) { + writeChanMap := make(map[string]chan ConnectionMsg) + connectionStateMap := make(map[string]int) + readChan := make(chan ConnectionMsg) + var connectionMutex sync.RWMutex + go CMListenForConnection(&connectionMutex, localPort, writeChanMap, + connectionStateMap, readChan) + for { + select { + case msgToPeer := <-msgChan: + switch msgToPeer.Type { + case "Data": + if state, exists := connectionStateMap[msgToPeer.Host]; exists && state == 1 { + /*FIXME/THINK: There could be deadlock if connectin closes before we will + be able to send to the chan */ + writeChan := writeChanMap[msgToPeer.Host] + writeChan <- msgToPeer + } else { + msgChan <- ConnectionMsg{Type: "ConnectionNotExist"} + } + case "Connect": + if len(strings.Split(msgToPeer.Host, ":")) > 1 { + radr := strings.Split(msgToPeer.Host, ":")[0] + connectionMutex.Lock() + if _, exist := writeChanMap[radr]; !exist { + writeChanMap[radr] = make(chan ConnectionMsg) + } + connectionMutex.Unlock() + peerTcpAddr, err := net.ResolveTCPAddr("tcp", msgToPeer.Host) + if err != nil { + //XXX: think about , mb make something less drastic + panic("cant resolve remote address") + } + go CMConnectToRemotePeer(&connectionMutex, peerTcpAddr, radr, + writeChanMap[radr], readChan, connectionStateMap) + } else { + connectionMutex.Lock() + if _, exist := writeChanMap[msgToPeer.Host]; !exist { + writeChanMap[msgToPeer.Host] = make(chan ConnectionMsg) + } + connectionMutex.Unlock() + remoteAddr := strings.Join([]string{msgToPeer.Host, strconv.Itoa(localPort)}, ":") + peerTcpAddr, err := net.ResolveTCPAddr("tcp", remoteAddr) + if err != nil { + //XXX: again panic could be overkill + panic("cant resolve remote address") + } + go CMConnectToRemotePeer(&connectionMutex, peerTcpAddr, msgToPeer.Host, + writeChanMap[msgToPeer.Host], readChan, connectionStateMap) + } + } + case msgFromPeer := <-readChan: + switch msgFromPeer.Type { + case "Data": + msgChan <- msgFromPeer + case "WriteError", "ReadError": + connectionMutex.Lock() + connectionStateMap[msgFromPeer.Host] = 0 + connectionMutex.Unlock() + if msgFromPeer.Type == "ReadError" { + writeChanMap[msgFromPeer.Host] <- ConnectionMsg{Type: "ConnectionError"} + } + var msgToApiClient ConnectionMsg + msgToApiClient.Host = msgFromPeer.Host + msgToApiClient.Type = "BufferFlush" + msgChan <- msgToApiClient + + } + } + } +} diff --git a/vendor/github.com/tehnerd/goUtils/netutils/tcp_listen.go b/vendor/github.com/tehnerd/goUtils/netutils/tcp_listen.go new file mode 100644 index 0000000..a7ad4fb --- /dev/null +++ b/vendor/github.com/tehnerd/goUtils/netutils/tcp_listen.go @@ -0,0 +1,42 @@ +package netutils + +import ( + "errors" + "net" +) + +/* + we need to provide a function, which will read/write to/from socket and read/write to/from sockets feedback chans +*/ +func ListenForConnection(port string, fn func(chan []byte, chan []byte, chan int, chan int)) error { + addr := ":" + port + tcpAddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return errors.New("cant resolve local tcp address") + } + loop := 1 + servSock, err := net.ListenTCP("tcp", tcpAddr) + if err != nil { + return errors.New("cant bind to local tcp address") + } + + for loop == 1 { + sock, err := servSock.AcceptTCP() + if err != nil { + continue + } + go ServeTcpConn(sock, fn) + } + return nil +} + +func ServeTcpConn(sock *net.TCPConn, fn func(chan []byte, chan []byte, chan int, chan int)) { + readChan := make(chan []byte) + writeChan := make(chan []byte) + feedbackFrom := make(chan int, 1) + feedbackTo := make(chan int, 1) + buf := make([]byte, 65535) + go ReadFromTCP(sock, buf, readChan, feedbackFrom) + go WriteToTCPrw(sock, writeChan, feedbackFrom, feedbackTo) + fn(readChan, writeChan, feedbackFrom, feedbackTo) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 10917fe..bbb1f06 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,6 +1,6 @@ -# github.com/gomodule/redigo v1.8.9 -## explicit; go 1.16 -github.com/gomodule/redigo/redis # github.com/leprosus/golang-ttl-map v1.1.7 ## explicit; go 1.15 github.com/leprosus/golang-ttl-map +# github.com/tehnerd/goUtils v0.0.0-20150515130609-5a2d8fb2ded8 +## explicit +github.com/tehnerd/goUtils/netutils