Commit c439cef78ee872fcfc59c4039d4dd7af4c31d639
1 parent
95baaa18
Exists in
master
提交欢乐招财猫初始化框架
Showing
49 changed files
with
9198 additions
and
0 deletions
Show diff stats
| ... | ... | @@ -0,0 +1,57 @@ |
| 1 | +package conf | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "encoding/xml" | |
| 5 | + "io/ioutil" | |
| 6 | + _ "strings" | |
| 7 | + | |
| 8 | + "common/logger" | |
| 9 | +) | |
| 10 | + | |
| 11 | +type RedisConf struct { | |
| 12 | + Host string `xml:",attr"` | |
| 13 | + Db uint32 `xml:",attr"` | |
| 14 | + Password string `xml:",attr"` | |
| 15 | +} | |
| 16 | + | |
| 17 | +type TexasConf struct { | |
| 18 | + ServerHttpAddr ServerHttpAddrConf `xml:"ServerHttpAddr"` | |
| 19 | + Redis RedisConf `xml:"Redis"` | |
| 20 | +} | |
| 21 | + | |
| 22 | +var ( | |
| 23 | + config = new(TexasConf) | |
| 24 | +) | |
| 25 | + | |
| 26 | +func GetRedisConf() RedisConf { | |
| 27 | + return config.Redis | |
| 28 | +} | |
| 29 | + | |
| 30 | +func LoadConf(filename string) error { | |
| 31 | + content, err := ioutil.ReadFile(filename) | |
| 32 | + if err != nil { | |
| 33 | + logger.Notic("read file:%v error:", filename, err) | |
| 34 | + return err | |
| 35 | + } | |
| 36 | + logger.Info("conf xml:%v", string(content)) | |
| 37 | + err = xml.Unmarshal(content, config) | |
| 38 | + if err != nil { | |
| 39 | + logger.Notic("decode xml error:%v", err) | |
| 40 | + return err | |
| 41 | + } | |
| 42 | + DumpConf() | |
| 43 | + return nil | |
| 44 | +} | |
| 45 | + | |
| 46 | +func DumpConf() { | |
| 47 | + logger.Info("--------------config dump----start--------") | |
| 48 | + logger.Info("--------------config dump----end--------") | |
| 49 | +} | |
| 50 | + | |
| 51 | +func GetServerHttpAddrConf() string { | |
| 52 | + return config.ServerHttpAddr.Host | |
| 53 | +} | |
| 54 | + | |
| 55 | +type ServerHttpAddrConf struct { | |
| 56 | + Host string `xml:",attr"` | |
| 57 | +} | ... | ... |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +[{"time":1,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":2,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":3,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":4,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":5,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":6,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":7,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":8,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":9,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":10,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":11,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":12,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":13,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":14,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":15,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":16,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":17,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":18,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":19,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":20,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":21,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":22,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":23,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":24,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":25,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":26,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":27,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":28,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":29,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":30,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":31,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":32,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":33,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":34,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":35,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":36,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":37,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":38,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":39,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":40,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":41,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":42,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":43,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":44,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":45,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":46,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":47,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":48,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":49,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":50,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":51,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":52,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":53,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":54,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":55,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":56,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":57,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":58,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":59,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":60,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":61,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":62,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":63,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":64,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":65,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":66,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":67,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":68,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":69,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":70,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":71,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":72,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":73,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":74,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":75,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":76,"type":3,"desc":"【分红】猫5min","flop_card":"[[\"a1\",\"a4\",\"a6\"],[\"b2\",\"b5\",\"b7\"],[\"c3\",\"c6\",\"c8\"],[\"a4\",\"a7\",\"a9\"],[\"b5\",\"b8\",\"b10\"],[\"c6\",\"c9\",\"c11\"],[\"a6\",\"a9\",\"a11\"],[\"b7\",\"b10\",\"b12\"]]"},{"time":77,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":78,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":79,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":80,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":81,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":82,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":83,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":84,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":85,"type":4,"desc":"【分红】猫10min","flop_card":"[[\"a2\",\"a3\",\"a4\"],[\"b3\",\"b4\",\"b5\"],[\"c4\",\"c5\",\"c6\"],[\"d5\",\"d6\",\"d7\"],[\"a6\",\"a7\",\"a8\"],[\"b7\",\"b8\",\"b9\"],[\"c8\",\"c9\",\"c10\"],[\"d9\",\"d10\",\"d11\"],[\"a10\",\"a11\",\"a12\"],[\"c11\",\"c12\",\"c13\"],[\"b12\",\"b13\",\"b1\"],[\"d12\",\"d13\",\"d1\"],[\"d2\",\"d3\",\"d4\"]]"},{"time":86,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":87,"type":1,"desc":"15min当前产出","flop_card":"[[\"a1\",\"b3\",\"c6\"],[\"b2\",\"a3\",\"d7\"],[\"c4\",\"b5\",\"c9\"],[\"d3\",\"c6\",\"d8\"],[\"a4\",\"b6\",\"c9\"],[\"b6\",\"a7\",\"d11\"],[\"d6\",\"c9\",\"d11\"],[\"a8\",\"b10\",\"c13\"],[\"c7\",\"b8\",\"c12\"],[\"d1\",\"c4\",\"d6\"],[\"a6\",\"b8\",\"c11\"],[\"d8\",\"c11\",\"d13\"],[\"c3\",\"b4\",\"c8\"]]"},{"time":88,"type":6,"desc":"赌博猫","flop_card":"[[\"a2\",\"b2\",\"c2\"],[\"b3\",\"c3\",\"d3\"],[\"a4\",\"b4\",\"d4\"],[\"a5\",\"c5\",\"d5\"],[\"a6\",\"b6\",\"c6\"],[\"b7\",\"c7\",\"d7\"],[\"a8\",\"b8\",\"d8\"],[\"a9\",\"c9\",\"d9\"],[\"a10\",\"b10\",\"c10\"],[\"b11\",\"c11\",\"d11\"],[\"a12\",\"b12\",\"d12\"],[\"a13\",\"c13\",\"d13\"]]"},{"time":89,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"},{"time":90,"type":2,"desc":"45min当前产出","flop_card":"[[\"a2\",\"b3\",\"c4\"],[\"b3\",\"c4\",\"d5\"],[\"c4\",\"d5\",\"a6\"],[\"a5\",\"b6\",\"c7\"],[\"b6\",\"c7\",\"d8\"],[\"c6\",\"d7\",\"a8\"],[\"a7\",\"b8\",\"c9\"],[\"a9\",\"b10\",\"c11\"],[\"c10\",\"d11\",\"a12\"],[\"a11\",\"b12\",\"c13\"],[\"b10\",\"c11\",\"d12\"],[\"c8\",\"d9\",\"a10\"],[\"a4\",\"b5\",\"c6\"]]"}] | |
| 0 | 2 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +[{"id":1,"name":"橘猫","level_buy":1,"rec_buy":1,"price":"210","gold_get":"1","ratio":1.07,"increse_limit":55,"interval":1},{"id":2,"name":"狮猫","level_buy":1,"rec_buy":1,"price":"2400","gold_get":"3","ratio":1.07,"increse_limit":52,"interval":1},{"id":3,"name":"黑白花","level_buy":1,"rec_buy":1,"price":"7200","gold_get":"7","ratio":1.07,"increse_limit":48,"interval":1},{"id":4,"name":"银渐层","level_buy":1,"rec_buy":1,"price":"18650","gold_get":"15","ratio":1.17,"increse_limit":28,"interval":1},{"id":5,"name":"银虎斑猫","level_buy":1,"rec_buy":1,"price":"49650","gold_get":"30","ratio":1.17,"increse_limit":24,"interval":1.5},{"id":6,"name":"孟买猫","level_buy":1,"rec_buy":1,"price":"132100","gold_get":"61","ratio":1.17,"increse_limit":23,"interval":1.5},{"id":7,"name":"截尾猫","level_buy":2,"rec_buy":1,"price":"351400","gold_get":"124","ratio":1.17,"increse_limit":22,"interval":1.5},{"id":8,"name":"蓝猫","level_buy":3,"rec_buy":1,"price":"934750","gold_get":"250","ratio":1.17,"increse_limit":21,"interval":1.5},{"id":9,"name":"三花猫","level_buy":4,"rec_buy":2,"price":"2486450","gold_get":"504","ratio":1.17,"increse_limit":20,"interval":2},{"id":10,"name":"狸花猫","level_buy":5,"rec_buy":3,"price":"6614000","gold_get":"1015","ratio":1.17,"increse_limit":20,"interval":2},{"id":11,"name":"玄猫","level_buy":6,"rec_buy":4,"price":"17593250","gold_get":"2043","ratio":1.17,"increse_limit":20,"interval":2},{"id":12,"name":"暖色布偶","level_buy":7,"rec_buy":5,"price":"46798050","gold_get":"4112","ratio":1.17,"increse_limit":19,"interval":2},{"id":13,"name":"卷毛猫","level_buy":8,"rec_buy":6,"price":"124482850","gold_get":"8276","ratio":1.17,"increse_limit":19,"interval":2.5},{"id":14,"name":"狸花猫","level_buy":9,"rec_buy":7,"price":"331124400","gold_get":"16657","ratio":1.17,"increse_limit":18,"interval":2.5},{"id":15,"name":"折耳猫","level_buy":10,"rec_buy":8,"price":"880790950","gold_get":"33523","ratio":1.17,"increse_limit":17,"interval":2.5},{"id":16,"name":"白暹罗","level_buy":11,"rec_buy":9,"price":"2342903950","gold_get":"67466","ratio":1.17,"increse_limit":16,"interval":2.5},{"id":17,"name":"银色缅因","level_buy":12,"rec_buy":10,"price":"6232124550","gold_get":"135778","ratio":1.17,"increse_limit":15,"interval":3},{"id":18,"name":"挪威猫","level_buy":13,"rec_buy":11,"price":"16577451350","gold_get":"273255","ratio":1.17,"increse_limit":14,"interval":3},{"id":19,"name":"虎纹纹猫","level_buy":14,"rec_buy":11,"price":"44096020600","gold_get":"549930","ratio":1.17,"increse_limit":13,"interval":3},{"id":20,"name":"短毛猫","level_buy":15,"rec_buy":12,"price":"117295414800","gold_get":"1106700","ratio":1.17,"increse_limit":12,"interval":3},{"id":21,"name":"森林猫","level_buy":16,"rec_buy":13,"price":"312005803400","gold_get":"2273000","ratio":1.17,"increse_limit":8,"interval":3.5},{"id":22,"name":"棕色暹罗","level_buy":17,"rec_buy":14,"price":"829935437050","gold_get":"4482600","ratio":1.17,"increse_limit":7,"interval":3.5},{"id":23,"name":"梵猫","level_buy":17,"rec_buy":15,"price":"2207628262600","gold_get":"9021200","ratio":1.17,"increse_limit":6,"interval":3.5},{"id":24,"name":"金吉拉","level_buy":18,"rec_buy":16,"price":"5872291178550","gold_get":"18155000","ratio":1.17,"increse_limit":5,"interval":3.5},{"id":25,"name":"虎纹缅因","level_buy":18,"rec_buy":16,"price":"15620294534950","gold_get":"36538000","ratio":1.17,"increse_limit":4,"interval":4},{"id":26,"name":"异国短毛","level_buy":19,"rec_buy":17,"price":"41549983463000","gold_get":"73533000","ratio":1.17,"increse_limit":4,"interval":4},{"id":27,"name":"豹猫","level_buy":20,"rec_buy":18,"price":"110522956011600","gold_get":"147990000","ratio":1.17,"increse_limit":4,"interval":4},{"id":28,"name":"波斯猫","level_buy":21,"rec_buy":19,"price":"293991062990900","gold_get":"297830000","ratio":1.17,"increse_limit":4,"interval":4},{"id":29,"name":"梵色布偶","level_buy":22,"rec_buy":20,"price":"782016227555800","gold_get":"599380000","ratio":1.17,"increse_limit":4,"interval":4.5},{"id":30,"name":"喜马拉雅","level_buy":23,"rec_buy":21,"price":"2080163165298450","gold_get":"1206300000","ratio":1.17,"increse_limit":4,"interval":4.5},{"id":31,"name":"无毛猫","level_buy":24,"rec_buy":22,"price":"5533234019693900","gold_get":"2427600000","ratio":1.17,"increse_limit":4,"interval":4.5},{"id":32,"name":"白橘猫","level_buy":25,"rec_buy":23,"price":"14718402492385800","gold_get":"4885600000","ratio":1.17,"increse_limit":4,"interval":4.5},{"id":33,"name":"胖橘","level_buy":26,"rec_buy":24,"price":"39150950629746250","gold_get":"9832400000","ratio":1.17,"increse_limit":4,"interval":5},{"id":34,"name":"美短","level_buy":27,"rec_buy":25,"price":"104141528675125000","gold_get":"19788000000","ratio":1.17,"increse_limit":4,"interval":5},{"id":35,"name":"黑白猫","level_buy":28,"rec_buy":26,"price":"277016466275832500","gold_get":"39823000000","ratio":1.17,"increse_limit":4,"interval":5},{"id":36,"name":"粉狮猫","level_buy":29,"rec_buy":27,"price":"736863800293715000","gold_get":"80145000000","ratio":1.17,"increse_limit":4,"interval":5},{"id":37,"name":"红包猫","level_buy":30,"rec_buy":28,"price":"1960057708781280000","gold_get":"80145000000","ratio":1.17,"increse_limit":4,"interval":5}] | |
| 0 | 2 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +[{"id":1,"name":"全球招财猫","skin":"abonus","money":0,"rate":0.002,"is_37":1,"dec_red":"每天获得平台收益分红20%","dec_get":"1.1周内邀请人数排名前10,<br/>官方将主动联系赠予"},{"id":2,"name":"1天招财猫","skin":"abonus","money":150,"rate":0,"is_37":1,"dec_red":"获得1天平台收益","dec_get":"1.2只36级猫合成可能获得<br/>2.五大神猫合成必得"},{"id":3,"name":"东方招财猫","skin":"dir1","money":0,"rate":300,"is_37":1,"dec_red":"集齐5大神猫,可召唤一天招财猫","dec_get":"1.2只36级猫合成可能获得"},{"id":4,"name":"南方招财猫","skin":"dir2","money":0,"rate":200,"is_37":1,"dec_red":"集齐5大神猫,可召唤一天招财猫","dec_get":"1.2只36级猫合成可能获得"},{"id":5,"name":"西方招财猫","skin":"dir3","money":0,"rate":200,"is_37":1,"dec_red":"集齐5大神猫,可召唤一天招财猫","dec_get":"1.2只36级猫合成可能获得"},{"id":6,"name":"北方招财猫","skin":"dir4","money":0,"rate":100,"is_37":1,"dec_red":"集齐5大神猫,可召唤一天招财猫","dec_get":"1.2只36级猫合成可能获得"},{"id":7,"name":"正中招财猫","skin":"dir5","money":0,"rate":0,"is_37":1,"dec_red":"集齐5大神猫,可召唤一天招财猫","dec_get":"1.2只36级猫合成可能获得"},{"id":8,"name":"情侣猫男","skin":"man","money":0,"rate":120,"is_37":1,"dec_red":"两只不同性别的情侣猫合成可获得52元红包","dec_get":"1.2只36级猫合成可能获得"},{"id":9,"name":"情侣猫女","skin":"woman","money":52,"rate":0,"is_37":1,"dec_red":"两只不同性别的情侣猫合成可获得52元红包","dec_get":"1.2只36级猫合成可能获得"},{"id":10,"name":"红包猫","skin":"redpacket","money":5,"rate":70,"is_37":0,"dec_red":"点击打开将直接获得5元红包","dec_get":"1.2只36级猫合成可能获得<br/>2.幸运扑克牌抽取获得<br/>3.大转盘抽奖获得"},{"id":11,"name":"发财猫","skin":"gambling","money":88,"rate":0,"is_37":0,"dec_red":"每期号码达到150,中午12.00开奖88元红包","dec_get":"1.幸运扑克牌抽取获得"},{"id":12,"name":"限时招财猫","skin":"abonus","money":0.17,"rate":0,"is_37":0,"dec_red":"可获得5-15min全平台分红","dec_get":"1.幸运扑克牌抽取获得"},{"id":13,"name":"10min招财猫","skin":"abonus","money":0.35,"rate":0,"is_37":0,"dec_red":"","dec_get":""},{"id":14,"name":"15min招财猫","skin":"abonus","money":1.563,"rate":0,"is_37":0,"dec_red":"","dec_get":""}] | |
| 0 | 2 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +[{"id":1,"type":1,"rate":0.3,"parameter":50,"desc":"海量金币","idx":4},{"id":2,"type":1,"rate":0.25,"parameter":45,"desc":"大量金币","idx":3},{"id":3,"type":1,"rate":0.15,"parameter":20,"desc":"中量金币","idx":2},{"id":4,"type":1,"rate":0.1,"parameter":15,"desc":"少量金币","idx":1},{"id":5,"type":1,"rate":0.2,"parameter":45,"desc":"大量金币","idx":3},{"id":6,"type":1,"rate":0,"parameter":1,"desc":"现金红包","idx":7},{"id":7,"type":2,"rate":80,"parameter":10,"desc":"10倍宝箱","idx":5},{"id":8,"type":2,"rate":20,"parameter":5,"desc":"5倍宝箱","idx":6}] | |
| 0 | 2 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,133 @@ |
| 1 | +package jsonconf | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "common/logger" | |
| 5 | + "encoding/json" | |
| 6 | + "io/ioutil" | |
| 7 | + "os" | |
| 8 | +) | |
| 9 | + | |
| 10 | +var ( | |
| 11 | + g_jsonconf = new(GameConfig) | |
| 12 | +) | |
| 13 | + | |
| 14 | +type CardDesc struct { | |
| 15 | + Time int `json:"time"` | |
| 16 | + Ctype int `json:"type"` | |
| 17 | + Desc string `json:"desc"` | |
| 18 | + Flop_card string `json:"flop_card"` | |
| 19 | +} | |
| 20 | + | |
| 21 | +type CatDesc struct { | |
| 22 | + Id int `json:"id"` | |
| 23 | + Name string `json:"name"` | |
| 24 | + Level_buy int `json:"level_buy"` | |
| 25 | + Rec_buy int `json:"rec_buy"` | |
| 26 | + Price int `json:"price"` | |
| 27 | + Gold_get int `json:"gold_get"` | |
| 28 | + Ratio float32 `json:"ratio"` | |
| 29 | + Increse_limit int `json:"increse_limit"` | |
| 30 | + Interval int `json:"interval"` | |
| 31 | +} | |
| 32 | + | |
| 33 | +type RedCatDesc struct { | |
| 34 | + Id int `json:"id"` | |
| 35 | + Name string `json:"name"` | |
| 36 | + Skin string `json:"skin"` | |
| 37 | + Money int `json:"money"` | |
| 38 | + Rate float32 `json:"rate"` | |
| 39 | + Is_37 int `json:"is_37"` | |
| 40 | + Dec_red string `json:"dec_red"` | |
| 41 | + Dec_get string `json:"dec_get"` | |
| 42 | +} | |
| 43 | + | |
| 44 | +type TurnTableDesc struct { | |
| 45 | + Id int `json:"id"` | |
| 46 | + Ttype int `json:"type"` | |
| 47 | + Rate float32 `json:"rate"` | |
| 48 | + Parameter int `json:"parameter"` | |
| 49 | + Desc string `json:"desc"` | |
| 50 | + Idx int `json:"idx"` | |
| 51 | +} | |
| 52 | + | |
| 53 | +type GameConfig struct { | |
| 54 | + CardConfig []CardDesc | |
| 55 | + CatConfig []CatDesc | |
| 56 | + RedCatConfig []RedCatDesc | |
| 57 | + TurnTableConfig []TurnTableDesc | |
| 58 | +} | |
| 59 | + | |
| 60 | +func GetJsonConf() *GameConfig { | |
| 61 | + return g_jsonconf | |
| 62 | +} | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | +func file_get_contents(path string) ([]byte, error) { | |
| 67 | + f, err := os.Open(path) | |
| 68 | + if err != nil { | |
| 69 | + return nil, err | |
| 70 | + } | |
| 71 | + return ioutil.ReadAll(f) | |
| 72 | +} | |
| 73 | + | |
| 74 | +func LoadJsonConf() error { | |
| 75 | + //pconf := &GameConfig{} | |
| 76 | + //加载第一个配置 | |
| 77 | + path := "../jsonconf/CardConfig.json" | |
| 78 | + content, err := file_get_contents(path) | |
| 79 | + if err != nil { | |
| 80 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 81 | + return err | |
| 82 | + } | |
| 83 | + | |
| 84 | + err = json.Unmarshal([]byte(content), &g_jsonconf.CardConfig) | |
| 85 | + if err != nil { | |
| 86 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 87 | + return err | |
| 88 | + } | |
| 89 | + | |
| 90 | + path = "../jsonconf/CatConfig.json" | |
| 91 | + content, err = file_get_contents(path) | |
| 92 | + if err != nil { | |
| 93 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 94 | + return err | |
| 95 | + } | |
| 96 | + | |
| 97 | + err = json.Unmarshal([]byte(content), &g_jsonconf.CatConfig) | |
| 98 | + if err != nil { | |
| 99 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 100 | + return err | |
| 101 | + } | |
| 102 | + | |
| 103 | + path = "../jsonconf/RedCatConfig.json" | |
| 104 | + content, err = file_get_contents(path) | |
| 105 | + if err != nil { | |
| 106 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 107 | + return err | |
| 108 | + } | |
| 109 | + | |
| 110 | + err = json.Unmarshal([]byte(content), &g_jsonconf.RedCatConfig) | |
| 111 | + if err != nil { | |
| 112 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 113 | + return err | |
| 114 | + } | |
| 115 | + | |
| 116 | + path = "../jsonconf/TurntableConfig.json" | |
| 117 | + content, err = file_get_contents(path) | |
| 118 | + if err != nil { | |
| 119 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 120 | + return err | |
| 121 | + } | |
| 122 | + | |
| 123 | + err = json.Unmarshal([]byte(content), &g_jsonconf.TurnTableConfig) | |
| 124 | + if err != nil { | |
| 125 | + logger.Info("loadJsonConf failed1,err=%v", err) | |
| 126 | + return err | |
| 127 | + } | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + logger.Info("loadJsonConf success pconf=%v,err=%v", *g_jsonconf, err) | |
| 132 | + return err | |
| 133 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,456 @@ |
| 1 | +package logic | |
| 2 | + | |
| 3 | +type UserLoginData struct { | |
| 4 | + //Uuid int `json:"uuid"` | |
| 5 | + Fromid int `json:"fromid"` | |
| 6 | + Sharetype int `json:"sharetype"` | |
| 7 | +} | |
| 8 | + | |
| 9 | +type UserLoginResp struct { | |
| 10 | + Code int `json:"code"` | |
| 11 | + Message string `json:"message"` | |
| 12 | + Gold int64 `json:"gold"` | |
| 13 | + Love int64 `json:"love"` | |
| 14 | + Goldrate int64 `json:"goldrate"` | |
| 15 | + Loverate int64 `json:"loverate"` | |
| 16 | + Highestlv int `json:"highestlv"` | |
| 17 | + Curboxlv int `json:"curboxlv"` | |
| 18 | + Isdoublegold int `json:"isdoublegold"` | |
| 19 | + Isauto int `json:"isauto"` | |
| 20 | + Offlinegold int64 `json:"offlinegold"` | |
| 21 | + Offlinelove int64 `json:"offlinelove"` | |
| 22 | + Data []DataDesc `json:"data"` | |
| 23 | +} | |
| 24 | + | |
| 25 | +type GetUserDataReq struct { | |
| 26 | + //Uuid int `json:"uuid"` | |
| 27 | +} | |
| 28 | + | |
| 29 | +type GetUserDataResp struct { | |
| 30 | + Code int `json:"code"` | |
| 31 | + Message string `json:"message"` | |
| 32 | + Gold int64 `json:"gold"` | |
| 33 | + Love int64 `json:"love"` | |
| 34 | + Dougoldlefttime int `json:"dougoldlefttime"` | |
| 35 | + Automlefttime int `json:"automlefttime"` | |
| 36 | + Acclcteboxlefttime int `json:"acclcteboxlefttime"` | |
| 37 | + Israndgift int `json:"israndgift"` | |
| 38 | +} | |
| 39 | + | |
| 40 | +type DataDesc struct { | |
| 41 | + Pos int `json:"pos"` | |
| 42 | + Catlv int `json:"cat_lv"` | |
| 43 | + Countdown int `json:"countdown"` | |
| 44 | +} | |
| 45 | + | |
| 46 | +type ExchangePosReq struct { | |
| 47 | + //Uuid int `json:"uuid"` | |
| 48 | + Pos_1 int `json:"pos_1"` | |
| 49 | + Pos_2 int `json:"pos_2"` | |
| 50 | +} | |
| 51 | + | |
| 52 | +type ExchangePosResp struct { | |
| 53 | + Code int `json:"code"` | |
| 54 | + Message string `json:"message"` | |
| 55 | + Pos1_lv int `json:"pos1_lv"` | |
| 56 | + Pos2_lv int `json:"pos2_lv"` | |
| 57 | + Highest_lv int `json:"highest_lv"` | |
| 58 | + Add_num int `json:"add_num"` | |
| 59 | +} | |
| 60 | + | |
| 61 | +type ClickBoxReq struct { | |
| 62 | + //Uuid int `json:"uuid"` | |
| 63 | + Pos int `json:"pos"` | |
| 64 | +} | |
| 65 | + | |
| 66 | +type ClickBoxResp struct { | |
| 67 | + Code int `json:"code"` | |
| 68 | + Message string `json:"message"` | |
| 69 | + Pos int `json:"pos"` | |
| 70 | + Lv int `json:"lv"` | |
| 71 | +} | |
| 72 | + | |
| 73 | +type UpgradeBoxReq struct { | |
| 74 | + //Uuid int `json:"uuid"` | |
| 75 | +} | |
| 76 | +type UpgradeBoxResp struct { | |
| 77 | + Code int `json:"code"` | |
| 78 | + Message string `json:"message"` | |
| 79 | + Boxlv int `json:"boxlv"` | |
| 80 | +} | |
| 81 | + | |
| 82 | +type GenerateBoxReq struct { | |
| 83 | + //Uuid int `json:"uuid"` | |
| 84 | + Pos int `json:"pos"` | |
| 85 | +} | |
| 86 | + | |
| 87 | +type GenerateBoxResp struct { | |
| 88 | + Code int `json:"code"` | |
| 89 | + Message string `json:"message"` | |
| 90 | +} | |
| 91 | + | |
| 92 | +type AcclecteReq struct { | |
| 93 | + //Uuid int `json:"uuid"` | |
| 94 | +} | |
| 95 | +type AcclecteResp struct { | |
| 96 | + Code int `json:"code"` | |
| 97 | + Message string `json:"message"` | |
| 98 | +} | |
| 99 | + | |
| 100 | +type AcclecteBoxResp struct { | |
| 101 | + Code int `json:"code"` | |
| 102 | + Message string `json:"message"` | |
| 103 | +} | |
| 104 | + | |
| 105 | +type AutomergeReq struct { | |
| 106 | + //Uuid int `json:"uuid"` | |
| 107 | +} | |
| 108 | +type AutomergeResp struct { | |
| 109 | + Code int `json:"code"` | |
| 110 | + Message string `json:"message"` | |
| 111 | +} | |
| 112 | + | |
| 113 | +type QueryBuyCatReq struct { | |
| 114 | + //Uuid int `json:"uuid"` | |
| 115 | +} | |
| 116 | +type QueryBuyCatResp struct { | |
| 117 | + Code int `json:"code"` | |
| 118 | + Message string `json:"message"` | |
| 119 | + Maxcatlv int `json:"maxcatlv"` | |
| 120 | + Data []BuyCatDesc `json:"data"` | |
| 121 | +} | |
| 122 | + | |
| 123 | +type BuyCatDesc struct { | |
| 124 | + Lv int `json:"lv"` | |
| 125 | + Goldnum int64 `json:"goldnum"` | |
| 126 | +} | |
| 127 | + | |
| 128 | +type ClickRandGiftReq struct { | |
| 129 | + //Uuid int `json:"uuid"` | |
| 130 | +} | |
| 131 | +type ClickRandGiftResp struct { | |
| 132 | + Code int `json:"code"` | |
| 133 | + Message string `json:"message"` | |
| 134 | + Gold int64 `json:"gold"` | |
| 135 | + Lefttime int `json:"lefttime"` | |
| 136 | +} | |
| 137 | + | |
| 138 | +type DoBuyCatReq struct { | |
| 139 | + Lv int `json:"lv"` | |
| 140 | +} | |
| 141 | +type DoBuyCatResp struct { | |
| 142 | + Code int `json:"code"` | |
| 143 | + Message string `json:"message"` | |
| 144 | + Pos int `json:"pos"` | |
| 145 | +} | |
| 146 | + | |
| 147 | +type QueryWareHouseResp struct { | |
| 148 | + Code int `json:"code"` | |
| 149 | + Message string `json:"message"` | |
| 150 | + Data []DataDesc `json:"data"` | |
| 151 | +} | |
| 152 | + | |
| 153 | +type QueryAutomergeResp struct { | |
| 154 | + Code int `json:"code"` | |
| 155 | + Message string `json:"message"` | |
| 156 | + Goldrate int64 `json:"goldrate"` | |
| 157 | + Higestlv int `json:"higestlv"` | |
| 158 | + Curcatjianum int `json:"curcatjianum"` | |
| 159 | + Data []DataDesc `json:"data"` | |
| 160 | +} | |
| 161 | + | |
| 162 | +type PutWareHouseReq struct { | |
| 163 | + Pos int `json:"pos"` | |
| 164 | +} | |
| 165 | +type PutWareHouseResp struct { | |
| 166 | + Code int `json:"code"` | |
| 167 | + Message string `json:"message"` | |
| 168 | + Warepos int `json:"warepos"` | |
| 169 | +} | |
| 170 | + | |
| 171 | +type TakeWareHouseReq struct { | |
| 172 | + Warepos int `json:"warepos"` | |
| 173 | +} | |
| 174 | +type TakeWareHouseResp struct { | |
| 175 | + Code int `json:"code"` | |
| 176 | + Message string `json:"message"` | |
| 177 | + Pos int `json:"pos"` | |
| 178 | +} | |
| 179 | + | |
| 180 | +type CatRoomDesc struct { | |
| 181 | + Pos int `json:"pos"` | |
| 182 | + Catlv int `json:"cat_lv"` | |
| 183 | +} | |
| 184 | + | |
| 185 | +type QueryCatRoomInfoResp struct { | |
| 186 | + Code int `json:"code"` | |
| 187 | + Message string `json:"message"` | |
| 188 | + Data []CatRoomDesc `json:"data"` | |
| 189 | +} | |
| 190 | + | |
| 191 | +type BuyCatRoomReq struct { | |
| 192 | + Lv int `json:"lv"` | |
| 193 | +} | |
| 194 | + | |
| 195 | +type BuyCatRoomResp struct { | |
| 196 | + Code int `json:"code"` | |
| 197 | + Message string `json:"message"` | |
| 198 | +} | |
| 199 | + | |
| 200 | +type UpCattoRoomReq struct { | |
| 201 | + Roompos int `json:"roompos"` | |
| 202 | + Callv int `json:"callv"` | |
| 203 | + Optype int `json:"optype"` | |
| 204 | +} | |
| 205 | + | |
| 206 | +type UpCattoRoommResp struct { | |
| 207 | + Code int `json:"code"` | |
| 208 | + Message string `json:"message"` | |
| 209 | +} | |
| 210 | + | |
| 211 | +type QueryCatShopInfoResp struct { | |
| 212 | + Code int `json:"code"` | |
| 213 | + Message string `json:"message"` | |
| 214 | + Chapter int `json:"chapter"` | |
| 215 | + Section int `json:"section"` | |
| 216 | + Lefttime int `json:"lefttime"` | |
| 217 | + Canwatch int `json:"canwatch"` | |
| 218 | + Storyhappen int `json:"storyhappen"` | |
| 219 | +} | |
| 220 | + | |
| 221 | +type CatShoPlayReq struct { | |
| 222 | + Catlv int `json:"catlv"` | |
| 223 | +} | |
| 224 | +type CatShoPlayResp struct { | |
| 225 | + Code int `json:"code"` | |
| 226 | + Message string `json:"message"` | |
| 227 | + Lefttime int `json:"lefttime"` | |
| 228 | + Canwatch int `json:"canwatch"` | |
| 229 | +} | |
| 230 | + | |
| 231 | +type GetCatShopRewardReq struct { | |
| 232 | + Optype int `json:"optype"` | |
| 233 | +} | |
| 234 | + | |
| 235 | +type GetCatShopRewardResp struct { | |
| 236 | + Code int `json:"code"` | |
| 237 | + Message string `json:"message"` | |
| 238 | + Love int64 `json:"love"` | |
| 239 | +} | |
| 240 | + | |
| 241 | +type AcclecteCatStoryResp struct { | |
| 242 | + Code int `json:"code"` | |
| 243 | + Message string `json:"message"` | |
| 244 | +} | |
| 245 | + | |
| 246 | +type UpdateUserInfoReq struct { | |
| 247 | + Headurl string `json:"headurl"` | |
| 248 | + Nickname string `json:"nickname"` | |
| 249 | + Realname string `json:"realname"` | |
| 250 | +} | |
| 251 | + | |
| 252 | +type UpdateUserInfoResp struct { | |
| 253 | + Code int `json:"code"` | |
| 254 | + Message string `json:"message"` | |
| 255 | +} | |
| 256 | + | |
| 257 | +type RankInfoDesc struct { | |
| 258 | + Rank int `json:"rank"` | |
| 259 | + Headurl string `json:"headurl"` | |
| 260 | + Nickname string `json:"nickname"` | |
| 261 | + Catlv int `json:"catlv"` | |
| 262 | + Goldnum int64 `json:"goldnum"` | |
| 263 | +} | |
| 264 | + | |
| 265 | +type QueryPlayerRankResp struct { | |
| 266 | + Code int `json:"code"` | |
| 267 | + Message string `json:"message"` | |
| 268 | + Data []RankInfoDesc `json:"data"` | |
| 269 | +} | |
| 270 | + | |
| 271 | +type QueryCompleteTaskDesc struct { | |
| 272 | + Taskid int `json:"taskid"` | |
| 273 | +} | |
| 274 | + | |
| 275 | +type QueryOnlienTaskDesc struct { | |
| 276 | + Taskid int `json:"taskid"` | |
| 277 | + Lefttime int64 `json:"lefttime"` | |
| 278 | +} | |
| 279 | + | |
| 280 | +type QueryCompleteTaskResp struct { | |
| 281 | + Code int `json:"code"` | |
| 282 | + Message string `json:"message"` | |
| 283 | + Data []QueryCompleteTaskDesc `json:"data"` | |
| 284 | + Online []QueryOnlienTaskDesc `json:"online"` | |
| 285 | +} | |
| 286 | + | |
| 287 | +type QueryCompleteAchievementDesc struct { | |
| 288 | + Achieveid int `json:"achieveid"` | |
| 289 | +} | |
| 290 | + | |
| 291 | +type QueryCompleteAchievementResp struct { | |
| 292 | + Code int `json:"code"` | |
| 293 | + Message string `json:"message"` | |
| 294 | + Data []QueryCompleteAchievementDesc `json:"data"` | |
| 295 | +} | |
| 296 | + | |
| 297 | +type GetTaskRewardReq struct { | |
| 298 | + Taskid int `json:"taskid"` | |
| 299 | +} | |
| 300 | + | |
| 301 | +type GetTaskRewardResp struct { | |
| 302 | + Code int `json:"code"` | |
| 303 | + Message string `json:"message"` | |
| 304 | +} | |
| 305 | + | |
| 306 | +type GetAchieveRewardReq struct { | |
| 307 | + Achieveid int `json:"achieveid"` | |
| 308 | +} | |
| 309 | + | |
| 310 | +type GetAchieveRewardResp struct { | |
| 311 | + Code int `json:"code"` | |
| 312 | + Message string `json:"message"` | |
| 313 | +} | |
| 314 | + | |
| 315 | +type StartOnlineTaskReq struct { | |
| 316 | + Taskid int `json:"taskid"` | |
| 317 | +} | |
| 318 | + | |
| 319 | +type StartOnlineTaskResp struct { | |
| 320 | + Code int `json:"code"` | |
| 321 | + Message string `json:"message"` | |
| 322 | + Lefttime int `json:"lefttime"` | |
| 323 | +} | |
| 324 | + | |
| 325 | +type GetOfflineRewardReq struct { | |
| 326 | + Optype int `json:"optype"` | |
| 327 | +} | |
| 328 | + | |
| 329 | +type GetOfflineRewardResp struct { | |
| 330 | + Code int `json:"code"` | |
| 331 | + Message string `json:"message"` | |
| 332 | + Gold int64 `json:"gold"` | |
| 333 | + Love int64 `json:"love"` | |
| 334 | +} | |
| 335 | + | |
| 336 | +//********************************************************************************************************** | |
| 337 | + | |
| 338 | +type PosData struct { | |
| 339 | + Pos int | |
| 340 | + Catlv int | |
| 341 | + Countdown int //倒计时 为0表示没有 | |
| 342 | + //UpPos int //上阵的位置 0表示未上阵 | |
| 343 | +} | |
| 344 | + | |
| 345 | +//玩家购买猫详情 | |
| 346 | +type BuyCatInfoData struct { | |
| 347 | + Buytime int //购买次数 | |
| 348 | + IsMaxBuytime int //是否达到涨价上线 1是0否 | |
| 349 | + CurPrice int64 //当前价格 | |
| 350 | +} | |
| 351 | + | |
| 352 | +//猫咖店家具数据 | |
| 353 | +type CatRoomData struct { | |
| 354 | + LvCatlv int //入住猫的等级 | |
| 355 | +} | |
| 356 | + | |
| 357 | +//猫咖门店数据 | |
| 358 | +type CatShopData struct { | |
| 359 | + Chapter int //当前所处进度 对应shopcaofig的id | |
| 360 | + Section int //当前小节进度 ,即当前大章节第几次故事 | |
| 361 | + IsPlaying int //是否处于探险模式 1是0否 | |
| 362 | + LeftTime int //探险剩余时间 | |
| 363 | + PlayTimes int //当前为第几次探险 | |
| 364 | + DayNum int //当天日期,用于判断跨天 | |
| 365 | + TotalWatchNumLeft int //当天剩余看视频次数 | |
| 366 | + ThisIsWatch int //本次探险是否已经看过视频 | |
| 367 | + IsMax int //是否已经通关 1是0否 | |
| 368 | + CurCatLv int //参加探险的猫等级 | |
| 369 | + IsStoryHappen int //此次是否触发故事 | |
| 370 | +} | |
| 371 | + | |
| 372 | +//玩家排行榜数据 | |
| 373 | +type UserRankInfo struct { | |
| 374 | + Gold int64 //金币 | |
| 375 | + Head string //头像地址 | |
| 376 | + NickName string //昵称 | |
| 377 | + Highestlv int //当前最高猫等级 | |
| 378 | + Uuid int //uuid | |
| 379 | +} | |
| 380 | + | |
| 381 | +//玩家任务数据 每日清零 | |
| 382 | +type TaskData struct { | |
| 383 | + //StartOnline int //开始计算在线时间 | |
| 384 | + StartOnline map[int]int64 //记录对应takid 和开始计时的时间 | |
| 385 | + BuyCatTime int //商店购买猫次数 | |
| 386 | + MergeTime int //合成猫次数 | |
| 387 | + PlayWithTime int //陪玩次数 | |
| 388 | + WatchAddTime int //看广告次数 | |
| 389 | + CompleteId map[int]int //已经完成的任务的id,对应task表的id value无用 | |
| 390 | + HaveComplete map[int]int //记录当日已完成的任务,已完成则不再完成 | |
| 391 | +} | |
| 392 | + | |
| 393 | +//玩家成就数据 | |
| 394 | +type AchieveMentData struct { | |
| 395 | + GetNewCatTime int //累计解锁新猫次数 | |
| 396 | + GetAllJia int //累计获得猫爬架次数 | |
| 397 | + GetRoomJu int //累计解锁新家具次数 | |
| 398 | + StoryTime int //累计解锁故事 | |
| 399 | + ShopTime int //累计解锁店铺 | |
| 400 | + CompleteId map[int]int //已经完成的任务的id,对应achievement表id value为无用 | |
| 401 | + HaveComplete map[int]int //记录已完成成就已完成则不再触发 | |
| 402 | +} | |
| 403 | + | |
| 404 | +//玩家数据 | |
| 405 | +type UserData struct { | |
| 406 | + Gold int64 //金币 | |
| 407 | + Love int64 //爱心值 | |
| 408 | + Goldrate int64 //金币生成速率 | |
| 409 | + Loverate int64 //爱心生产速率 | |
| 410 | + Highestlv int //当前最高猫等级 | |
| 411 | + InviteId int //邀请者uid | |
| 412 | + CurBoxLv int //当前猫箱子等级 | |
| 413 | + IsDouble int //当前加速标签 1表示双倍收益 0表示正常 | |
| 414 | + IsAuto int //当前是否自动合成 | |
| 415 | + IsBoxAcc int //是否处于加速生成箱子状态 | |
| 416 | + RandGiftNum int //当前剩余空投猫粮次数 | |
| 417 | + RandGiftDay int //记录当前猫粮日期,当日期变化则重置RandGiftNum | |
| 418 | + RandGiftTime int //记录上一次空投猫粮时间 | |
| 419 | + Redbag int //红包值 单位为分 | |
| 420 | + Head string //头像地址 | |
| 421 | + NickName string //昵称 | |
| 422 | + RealName string //实名 | |
| 423 | + IsFirstRedBgCat int //是否合成过红包猫 0表示否1表示是 | |
| 424 | + OfflineGold int64 //离线金币 | |
| 425 | + OfflineLove int64 //离线爱心 | |
| 426 | + CatShopInfo CatShopData //猫咖门店数据 | |
| 427 | + Taskinfo TaskData //任务数据 | |
| 428 | + AchieveMent AchieveMentData //成就数据 | |
| 429 | + PosInfo []PosData //位置信息 从0开始 | |
| 430 | + BuyCatInfo []BuyCatInfoData //商店购买猫数据 第一个元素为1级猫 第二个为2级猫以此类推 | |
| 431 | + CatRoomInfo []CatRoomData //猫咖店数据 | |
| 432 | + | |
| 433 | +} | |
| 434 | + | |
| 435 | +//仓库数据详情 | |
| 436 | +type WareHouseDesc struct { | |
| 437 | + Warelv int //红包猫等级 对应表id | |
| 438 | +} | |
| 439 | + | |
| 440 | +//玩家仓库数据 | |
| 441 | +type UserWareHouseData struct { | |
| 442 | + Info []WareHouseDesc //下标表示位置 | |
| 443 | +} | |
| 444 | + | |
| 445 | +const ( | |
| 446 | + TASK_TYPE_ONLINE = 1 //在线 | |
| 447 | + TASK_TYPE_BUYCAT = 2 //商店购买猫 | |
| 448 | + TASK_TYPE_MERGE = 3 //合成猫 | |
| 449 | + TASK_TYPE_PLAYWITHCAT = 4 //猫咪陪玩 | |
| 450 | + TASK_TYPE_WATCHADD = 5 //观看广告 | |
| 451 | + ACH_TYPE_GETCAT = 6 //累计解锁猫 | |
| 452 | + ACH_TYPE_GETCATJIA = 7 //累计获得猫爬架 | |
| 453 | + ACH_TYPE_GETCATROOMJIA = 8 //累计解锁新家具 | |
| 454 | + ACH_TYPE_GETSTORY = 9 //累计解锁新故事 | |
| 455 | + ACH_TYPE_GBESHOP = 10 //累计用于新店铺 | |
| 456 | +) | ... | ... |
| ... | ... | @@ -0,0 +1,792 @@ |
| 1 | +package logic | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "HttpServer/conf" | |
| 5 | + "bytes" | |
| 6 | + "common/logger" | |
| 7 | + "encoding/json" | |
| 8 | + "strconv" | |
| 9 | + | |
| 10 | + //"encoding/json" | |
| 11 | + "fmt" | |
| 12 | + "io/ioutil" | |
| 13 | + //"log" | |
| 14 | + "net/http" | |
| 15 | +) | |
| 16 | + | |
| 17 | + | |
| 18 | +func StartHttpServe() { | |
| 19 | + startServerHttpServe() | |
| 20 | +} | |
| 21 | + | |
| 22 | +type QueryInviteReq struct { | |
| 23 | + Uuid int `json:"uuid"` | |
| 24 | +} | |
| 25 | + | |
| 26 | +type FetchReward struct { | |
| 27 | + Gameid string `json:"gameid"` | |
| 28 | + Channel string `json:"channel"` | |
| 29 | + Tasktype int `json:"tasktype"` | |
| 30 | + Taskid int `json:"taskid"` | |
| 31 | +} | |
| 32 | + | |
| 33 | +//just for test | |
| 34 | +func Testsendhttp() { | |
| 35 | + /*var queryinb QueryInviteReq | |
| 36 | + queryinb.Uuid = 131532 | |
| 37 | + client := &http.Client{} | |
| 38 | + | |
| 39 | + bys, err := json.Marshal(&queryinb) | |
| 40 | + if err != nil { | |
| 41 | + logger.Error("testsendhttp failed=%v", err) | |
| 42 | + return | |
| 43 | + } | |
| 44 | + body := bytes.NewBuffer(bys) | |
| 45 | + url := "https://catcafeapi.puchigame.com/catcafe/user/queryInvite" | |
| 46 | + reqest, err := http.NewRequest("POST", url, body) | |
| 47 | + if err != nil { | |
| 48 | + logger.Error("http.NewRequest failed") | |
| 49 | + } | |
| 50 | + //reqest.Header.Add("Uuid", "101") | |
| 51 | + //发送 | |
| 52 | + //res, err := http.Post(url, "application/json;charset=utf-8", body) | |
| 53 | + res, err := client.Do(reqest) | |
| 54 | + if err != nil { | |
| 55 | + logger.Error(" post failed to %v err:%v data:%v", url, err, string(bys)) | |
| 56 | + return | |
| 57 | + } | |
| 58 | + | |
| 59 | + result, _ := ioutil.ReadAll(res.Body) | |
| 60 | + res.Body.Close() | |
| 61 | + | |
| 62 | + s := string(result) | |
| 63 | + logger.Info("Testsendhttp result=%v", s)*/ | |
| 64 | + | |
| 65 | + client1 := &http.Client{} | |
| 66 | + var fr FetchReward | |
| 67 | + fr.Gameid = "1" | |
| 68 | + fr.Channel = "2" | |
| 69 | + fr.Tasktype = 2 | |
| 70 | + fr.Taskid = 1 | |
| 71 | + bys, err := json.Marshal(&fr) | |
| 72 | + if err != nil { | |
| 73 | + logger.Error("testsendhttp failed=%v", err) | |
| 74 | + return | |
| 75 | + } | |
| 76 | + body := bytes.NewBuffer(bys) | |
| 77 | + url1 := "http://192.144.198.179:50058/eliminatestar/gettaskreward" | |
| 78 | + reqest, err := http.NewRequest("POST", url1, body) | |
| 79 | + if err != nil { | |
| 80 | + logger.Error("http.NewRequest failed") | |
| 81 | + } | |
| 82 | + reqest.Header.Add("Uuid", "13") | |
| 83 | + //发送 | |
| 84 | + //res, err := http.Post(url1, "application/json;charset=utf-8", body) | |
| 85 | + if err != nil { | |
| 86 | + logger.Error("http.Post failed err=%v", err) | |
| 87 | + } | |
| 88 | + res, err := client1.Do(reqest) | |
| 89 | + if err != nil { | |
| 90 | + logger.Error(" post failed to %v err:%v data:%v", url1, err, string(bys)) | |
| 91 | + return | |
| 92 | + } | |
| 93 | + | |
| 94 | + result, _ := ioutil.ReadAll(res.Body) | |
| 95 | + res.Body.Close() | |
| 96 | + | |
| 97 | + s := string(result) | |
| 98 | + logger.Info("Testsendhttp result=%v", s) | |
| 99 | + return | |
| 100 | + | |
| 101 | + /*var test UserLoginData | |
| 102 | + //test.Uuid = 100 | |
| 103 | + test.Fromid = 200 | |
| 104 | + test.Sharetype = 1 | |
| 105 | + | |
| 106 | + client := &http.Client{} | |
| 107 | + | |
| 108 | + bys, err := json.Marshal(&test) | |
| 109 | + if err != nil { | |
| 110 | + logger.Error("testsendhttp failed=%v", err) | |
| 111 | + return | |
| 112 | + } | |
| 113 | + body := bytes.NewBuffer(bys) | |
| 114 | + url := "http://127.0.0.1:50056/cat/login" | |
| 115 | + reqest, err := http.NewRequest("POST", url, body) | |
| 116 | + if err != nil { | |
| 117 | + logger.Error("http.NewRequest failed") | |
| 118 | + } | |
| 119 | + reqest.Header.Add("Uuid", "101") | |
| 120 | + //发送 | |
| 121 | + //res, err := http.Post(url, "application/json;charset=utf-8", body) | |
| 122 | + res, err := client.Do(reqest) | |
| 123 | + if err != nil { | |
| 124 | + logger.Error(" post failed to %v err:%v data:%v", url, err, string(bys)) | |
| 125 | + return | |
| 126 | + } | |
| 127 | + | |
| 128 | + result, _ := ioutil.ReadAll(res.Body) | |
| 129 | + res.Body.Close() | |
| 130 | + | |
| 131 | + s := string(result) | |
| 132 | + var resp GetUserDataResp | |
| 133 | + resp.Code = 0 | |
| 134 | + var rdata UserLoginResp | |
| 135 | + _ = json.Unmarshal([]byte(s), &rdata) | |
| 136 | + logger.Info("testsendhttp , body:%v", rdata) | |
| 137 | + | |
| 138 | + url = "https://api.weixin.qq.com/wxa/msg_sec_check?access_token=32_d50mJQLwHa87YFPGn-WqGo7wAz_92Q7pN5yWsFJKGP1kB3UiEWTXvgR7Z0X_qYBID52aano4IDOU93LdDiNWbRhrrwbqOdTeeH1grKJ_gqUz0m1AzKAbytocmhRE-cf7GaGFD9aUSKLquRelFDCfAAAVTS" | |
| 139 | + type DDT struct { | |
| 140 | + Content string `json:"content"` | |
| 141 | + } | |
| 142 | + var ddt DDT | |
| 143 | + ddt.Content = "fuck you" | |
| 144 | + bys, err = json.Marshal(&ddt) | |
| 145 | + if err != nil { | |
| 146 | + logger.Error("testsendhttp2 failed=%v", err) | |
| 147 | + return | |
| 148 | + } | |
| 149 | + | |
| 150 | + body = bytes.NewBuffer(bys) | |
| 151 | + res, err = http.Post(url, "application/json;charset=utf-8", body) | |
| 152 | + if err != nil { | |
| 153 | + logger.Error(" post failed to %v err:%v data:%v", url, err, string(bys)) | |
| 154 | + return | |
| 155 | + } | |
| 156 | + | |
| 157 | + result, _ = ioutil.ReadAll(res.Body) | |
| 158 | + res.Body.Close() | |
| 159 | + | |
| 160 | + s = string(result) | |
| 161 | + | |
| 162 | + logger.Info("testsendhttp2222222222 , body:%v", s)*/ | |
| 163 | +} | |
| 164 | + | |
| 165 | +func CheckErr(err error) { | |
| 166 | + if err != nil { | |
| 167 | + panic(err) | |
| 168 | + } | |
| 169 | +} | |
| 170 | + | |
| 171 | +func startServerHttpServe() { | |
| 172 | + http.HandleFunc("/cat/login", UserLogin) //登录 | |
| 173 | + http.HandleFunc("/cat/getuserdata", GetUserData) //拉取用户数据 | |
| 174 | + http.HandleFunc("/cat/exchangetwoPos", ExchangePos) //交换位置 | |
| 175 | + http.HandleFunc("/cat/clickcatbox", ClickCatBox) //请求点击猫箱子 | |
| 176 | + http.HandleFunc("/cat/upgradecatbox", UpgradeCatBox) //请求点击猫箱子 | |
| 177 | + http.HandleFunc("/cat/acclecte", AcclecteGold) //请求点击猫箱子 | |
| 178 | + http.HandleFunc("/cat/automerge", AutoMerge) //请求自动合成 | |
| 179 | + http.HandleFunc("/cat/generatebox", GenerateBox) //请求点击猫箱子 | |
| 180 | + http.HandleFunc("/cat/clickrandgift", ClickRandGift) //请求点击猫箱子 | |
| 181 | + http.HandleFunc("/cat/querybuycat", QueryBuyCat) //请求商店购买信息 | |
| 182 | + http.HandleFunc("/cat/dobuycat", DoBuyCat) //购买猫 | |
| 183 | + http.HandleFunc("/cat/querwarehouse", QueryWareHouse) //请求仓库信息 | |
| 184 | + http.HandleFunc("/cat/putcattowarehouse", PutCattoWareHouse) //将合成界面的猫放入仓库 | |
| 185 | + http.HandleFunc("/cat/takecatoutfromwarehouse", TakeCatoutfromWareHouse) //将仓库的猫取出 | |
| 186 | + http.HandleFunc("/cat/acclecteboxrate ", AcclecteBoxRate) //请求点击猫箱子 | |
| 187 | + http.HandleFunc("/cat/queryautomergeinfo ", QueryAutomergeInfo) //请求点击猫箱子 | |
| 188 | + http.HandleFunc("/cat/querycatroominfo ", QueryCatRoomInfo) //请求点击猫箱子 | |
| 189 | + http.HandleFunc("/cat/buycatroom ", BuyCatRoom) //请求点击猫箱子 | |
| 190 | + http.HandleFunc("/cat/upcattoroom ", UpCattoRoom) //请求点击猫箱子 | |
| 191 | + http.HandleFunc("/cat/querycatshopinfo ", QueryCatShopInfo) //请求点击猫箱子 | |
| 192 | + http.HandleFunc("/cat/catshoplay ", CatShoPlay) //请求点击猫箱子 | |
| 193 | + http.HandleFunc("/cat/getcatshopreward ", GetCatShopReward) //请求点击猫箱子 | |
| 194 | + http.HandleFunc("/cat/acclectecatstory ", AcclecteCatStory) //请求点击猫箱子 | |
| 195 | + http.HandleFunc("/cat/updateuserinfo ", UpdateUserInfo) //请求点击猫箱子 | |
| 196 | + http.HandleFunc("/cat/queryplayerrank ", QueryPlayerRank) //请求点击猫箱子 | |
| 197 | + http.HandleFunc("/cat/querycompletetask ", QueryCompleteTask) //请求点击猫箱子 | |
| 198 | + http.HandleFunc("/cat/querycompleteachievement ", QueryCompleteAchievement) //请求点击猫箱子 | |
| 199 | + http.HandleFunc("/cat/gettaskreward ", GetTaskReward) //请求点击猫箱子 | |
| 200 | + http.HandleFunc("/cat/getachievereward ", GetAchieveReward) //请求点击猫箱子 | |
| 201 | + http.HandleFunc("/cat/startonlinetask ", StartOnlineTask) //请求开始在线时长任务 | |
| 202 | + http.HandleFunc("/cat/getofflinereward ", GetOfflineReward) //请求开始在线时长任务 | |
| 203 | + | |
| 204 | + err := http.ListenAndServe(conf.GetServerHttpAddrConf(), nil) | |
| 205 | + CheckErr(err) | |
| 206 | +} | |
| 207 | + | |
| 208 | +func GetOfflineReward(w http.ResponseWriter, r *http.Request) { | |
| 209 | + Uuid := 0 | |
| 210 | + if len(r.Header) > 0 { | |
| 211 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 212 | + } | |
| 213 | + if Uuid == 0 { | |
| 214 | + SetHeader(w) | |
| 215 | + return | |
| 216 | + } | |
| 217 | + result, _ := ioutil.ReadAll(r.Body) | |
| 218 | + r.Body.Close() | |
| 219 | + | |
| 220 | + s := string(result) | |
| 221 | + logger.Info("GetOfflineReward , body:%v,uuid=%v", s, Uuid) | |
| 222 | + | |
| 223 | + //HandlerGetOfflineReward(w, s, Uuid) | |
| 224 | +} | |
| 225 | + | |
| 226 | +func StartOnlineTask(w http.ResponseWriter, r *http.Request) { | |
| 227 | + Uuid := 0 | |
| 228 | + if len(r.Header) > 0 { | |
| 229 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 230 | + } | |
| 231 | + if Uuid == 0 { | |
| 232 | + SetHeader(w) | |
| 233 | + return | |
| 234 | + } | |
| 235 | + result, _ := ioutil.ReadAll(r.Body) | |
| 236 | + r.Body.Close() | |
| 237 | + | |
| 238 | + s := string(result) | |
| 239 | + logger.Info("StartOnlineTask , body:%v,uuid=%v", s, Uuid) | |
| 240 | + | |
| 241 | + //HandlerStartOnlineTask(w, s, Uuid) | |
| 242 | +} | |
| 243 | + | |
| 244 | +func QueryCompleteTask(w http.ResponseWriter, r *http.Request) { | |
| 245 | + Uuid := 0 | |
| 246 | + if len(r.Header) > 0 { | |
| 247 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 248 | + } | |
| 249 | + if Uuid == 0 { | |
| 250 | + SetHeader(w) | |
| 251 | + return | |
| 252 | + } | |
| 253 | + result, _ := ioutil.ReadAll(r.Body) | |
| 254 | + r.Body.Close() | |
| 255 | + | |
| 256 | + s := string(result) | |
| 257 | + logger.Info("QueryCompleteTask , body:%v,uuid=%v", s, Uuid) | |
| 258 | + | |
| 259 | + //HandlerQueryCompleteTask(w, s, Uuid) | |
| 260 | +} | |
| 261 | + | |
| 262 | +func QueryCompleteAchievement(w http.ResponseWriter, r *http.Request) { | |
| 263 | + Uuid := 0 | |
| 264 | + if len(r.Header) > 0 { | |
| 265 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 266 | + } | |
| 267 | + if Uuid == 0 { | |
| 268 | + SetHeader(w) | |
| 269 | + return | |
| 270 | + } | |
| 271 | + result, _ := ioutil.ReadAll(r.Body) | |
| 272 | + r.Body.Close() | |
| 273 | + | |
| 274 | + s := string(result) | |
| 275 | + logger.Info("QueryCompleteAchievement , body:%v,uuid=%v", s, Uuid) | |
| 276 | + | |
| 277 | + //HandlerQueryCompleteAchievement(w, s, Uuid) | |
| 278 | +} | |
| 279 | + | |
| 280 | +func GetTaskReward(w http.ResponseWriter, r *http.Request) { | |
| 281 | + Uuid := 0 | |
| 282 | + if len(r.Header) > 0 { | |
| 283 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 284 | + } | |
| 285 | + if Uuid == 0 { | |
| 286 | + SetHeader(w) | |
| 287 | + return | |
| 288 | + } | |
| 289 | + result, _ := ioutil.ReadAll(r.Body) | |
| 290 | + r.Body.Close() | |
| 291 | + | |
| 292 | + s := string(result) | |
| 293 | + logger.Info("GetTaskReward , body:%v,uuid=%v", s, Uuid) | |
| 294 | + | |
| 295 | + //HandlerGetTaskReward(w, s, Uuid) | |
| 296 | +} | |
| 297 | + | |
| 298 | +func GetAchieveReward(w http.ResponseWriter, r *http.Request) { | |
| 299 | + Uuid := 0 | |
| 300 | + if len(r.Header) > 0 { | |
| 301 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 302 | + } | |
| 303 | + if Uuid == 0 { | |
| 304 | + SetHeader(w) | |
| 305 | + return | |
| 306 | + } | |
| 307 | + result, _ := ioutil.ReadAll(r.Body) | |
| 308 | + r.Body.Close() | |
| 309 | + | |
| 310 | + s := string(result) | |
| 311 | + logger.Info("GetAchieveReward , body:%v,uuid=%v", s, Uuid) | |
| 312 | + | |
| 313 | + //HandlerGetAchieveReward(w, s, Uuid) | |
| 314 | +} | |
| 315 | + | |
| 316 | +func UpdateUserInfo(w http.ResponseWriter, r *http.Request) { | |
| 317 | + Uuid := 0 | |
| 318 | + if len(r.Header) > 0 { | |
| 319 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 320 | + } | |
| 321 | + if Uuid == 0 { | |
| 322 | + SetHeader(w) | |
| 323 | + return | |
| 324 | + } | |
| 325 | + result, _ := ioutil.ReadAll(r.Body) | |
| 326 | + r.Body.Close() | |
| 327 | + | |
| 328 | + s := string(result) | |
| 329 | + logger.Info("UpdateUserInfo , body:%v,uuid=%v", s, Uuid) | |
| 330 | + | |
| 331 | + //HandlerUpdateUserInfo(w, s, Uuid) | |
| 332 | +} | |
| 333 | + | |
| 334 | +func QueryPlayerRank(w http.ResponseWriter, r *http.Request) { | |
| 335 | + Uuid := 0 | |
| 336 | + if len(r.Header) > 0 { | |
| 337 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 338 | + } | |
| 339 | + if Uuid == 0 { | |
| 340 | + SetHeader(w) | |
| 341 | + return | |
| 342 | + } | |
| 343 | + result, _ := ioutil.ReadAll(r.Body) | |
| 344 | + r.Body.Close() | |
| 345 | + | |
| 346 | + s := string(result) | |
| 347 | + logger.Info("QueryPlayerRank , body:%v,uuid=%v", s, Uuid) | |
| 348 | + | |
| 349 | + //HandlerQueryPlayerRank(w, s, Uuid) | |
| 350 | +} | |
| 351 | + | |
| 352 | +func AcclecteCatStory(w http.ResponseWriter, r *http.Request) { | |
| 353 | + Uuid := 0 | |
| 354 | + if len(r.Header) > 0 { | |
| 355 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 356 | + } | |
| 357 | + if Uuid == 0 { | |
| 358 | + SetHeader(w) | |
| 359 | + return | |
| 360 | + } | |
| 361 | + result, _ := ioutil.ReadAll(r.Body) | |
| 362 | + r.Body.Close() | |
| 363 | + | |
| 364 | + s := string(result) | |
| 365 | + logger.Info("AcclecteCatStory , body:%v,uuid=%v", s, Uuid) | |
| 366 | + | |
| 367 | + //HandlerAcclecteCatStory(w, s, Uuid) | |
| 368 | +} | |
| 369 | + | |
| 370 | +func QueryCatShopInfo(w http.ResponseWriter, r *http.Request) { | |
| 371 | + Uuid := 0 | |
| 372 | + if len(r.Header) > 0 { | |
| 373 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 374 | + } | |
| 375 | + if Uuid == 0 { | |
| 376 | + SetHeader(w) | |
| 377 | + return | |
| 378 | + } | |
| 379 | + result, _ := ioutil.ReadAll(r.Body) | |
| 380 | + r.Body.Close() | |
| 381 | + | |
| 382 | + s := string(result) | |
| 383 | + logger.Info("QueryCatShopInfo , body:%v,uuid=%v", s, Uuid) | |
| 384 | + | |
| 385 | + //HandlerQueryCatShopInfo(w, s, Uuid) | |
| 386 | +} | |
| 387 | + | |
| 388 | +func CatShoPlay(w http.ResponseWriter, r *http.Request) { | |
| 389 | + Uuid := 0 | |
| 390 | + if len(r.Header) > 0 { | |
| 391 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 392 | + } | |
| 393 | + if Uuid == 0 { | |
| 394 | + SetHeader(w) | |
| 395 | + return | |
| 396 | + } | |
| 397 | + result, _ := ioutil.ReadAll(r.Body) | |
| 398 | + r.Body.Close() | |
| 399 | + | |
| 400 | + s := string(result) | |
| 401 | + logger.Info("CatShoPlay , body:%v,uuid=%v", s, Uuid) | |
| 402 | + | |
| 403 | + //HandlerCatShoPlay(w, s, Uuid) | |
| 404 | +} | |
| 405 | + | |
| 406 | +func GetCatShopReward(w http.ResponseWriter, r *http.Request) { | |
| 407 | + Uuid := 0 | |
| 408 | + if len(r.Header) > 0 { | |
| 409 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 410 | + } | |
| 411 | + if Uuid == 0 { | |
| 412 | + SetHeader(w) | |
| 413 | + return | |
| 414 | + } | |
| 415 | + result, _ := ioutil.ReadAll(r.Body) | |
| 416 | + r.Body.Close() | |
| 417 | + | |
| 418 | + s := string(result) | |
| 419 | + logger.Info("GetCatShopReward , body:%v,uuid=%v", s, Uuid) | |
| 420 | + | |
| 421 | + //HandlerGetCatShopReward(w, s, Uuid) | |
| 422 | +} | |
| 423 | + | |
| 424 | +func QueryCatRoomInfo(w http.ResponseWriter, r *http.Request) { | |
| 425 | + Uuid := 0 | |
| 426 | + if len(r.Header) > 0 { | |
| 427 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 428 | + } | |
| 429 | + if Uuid == 0 { | |
| 430 | + SetHeader(w) | |
| 431 | + return | |
| 432 | + } | |
| 433 | + result, _ := ioutil.ReadAll(r.Body) | |
| 434 | + r.Body.Close() | |
| 435 | + | |
| 436 | + s := string(result) | |
| 437 | + logger.Info("QueryCatRoomInfo , body:%v,uuid=%v", s, Uuid) | |
| 438 | + | |
| 439 | + //HandlerQueryCatRoomInfo(w, s, Uuid) | |
| 440 | +} | |
| 441 | + | |
| 442 | +func BuyCatRoom(w http.ResponseWriter, r *http.Request) { | |
| 443 | + Uuid := 0 | |
| 444 | + if len(r.Header) > 0 { | |
| 445 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 446 | + } | |
| 447 | + if Uuid == 0 { | |
| 448 | + SetHeader(w) | |
| 449 | + return | |
| 450 | + } | |
| 451 | + result, _ := ioutil.ReadAll(r.Body) | |
| 452 | + r.Body.Close() | |
| 453 | + | |
| 454 | + s := string(result) | |
| 455 | + logger.Info("BuyCatRoom , body:%v,uuid=%v", s, Uuid) | |
| 456 | + | |
| 457 | + //HandlerBuyCatRoom(w, s, Uuid) | |
| 458 | +} | |
| 459 | + | |
| 460 | +func UpCattoRoom(w http.ResponseWriter, r *http.Request) { | |
| 461 | + Uuid := 0 | |
| 462 | + if len(r.Header) > 0 { | |
| 463 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 464 | + } | |
| 465 | + if Uuid == 0 { | |
| 466 | + SetHeader(w) | |
| 467 | + return | |
| 468 | + } | |
| 469 | + result, _ := ioutil.ReadAll(r.Body) | |
| 470 | + r.Body.Close() | |
| 471 | + | |
| 472 | + s := string(result) | |
| 473 | + logger.Info("QueryCatRoomInfo , body:%v,uuid=%v", s, Uuid) | |
| 474 | + | |
| 475 | + //HandlerUpCattoRoom(w, s, Uuid) | |
| 476 | +} | |
| 477 | + | |
| 478 | +func QueryAutomergeInfo(w http.ResponseWriter, r *http.Request) { | |
| 479 | + Uuid := 0 | |
| 480 | + if len(r.Header) > 0 { | |
| 481 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 482 | + } | |
| 483 | + if Uuid == 0 { | |
| 484 | + SetHeader(w) | |
| 485 | + return | |
| 486 | + } | |
| 487 | + result, _ := ioutil.ReadAll(r.Body) | |
| 488 | + r.Body.Close() | |
| 489 | + | |
| 490 | + s := string(result) | |
| 491 | + logger.Info("QueryAutomergeInfo , body:%v,uuid=%v", s, Uuid) | |
| 492 | + | |
| 493 | + //HandlerQueryAutomergeInfo(w, s, Uuid) | |
| 494 | +} | |
| 495 | + | |
| 496 | +func QueryWareHouse(w http.ResponseWriter, r *http.Request) { | |
| 497 | + Uuid := 0 | |
| 498 | + if len(r.Header) > 0 { | |
| 499 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 500 | + } | |
| 501 | + if Uuid == 0 { | |
| 502 | + SetHeader(w) | |
| 503 | + return | |
| 504 | + } | |
| 505 | + result, _ := ioutil.ReadAll(r.Body) | |
| 506 | + r.Body.Close() | |
| 507 | + | |
| 508 | + s := string(result) | |
| 509 | + logger.Info("QueryWareHouse , body:%v,uuid=%v", s, Uuid) | |
| 510 | + | |
| 511 | + //HandlerQueryWareHouse(w, s, Uuid) | |
| 512 | +} | |
| 513 | + | |
| 514 | +func PutCattoWareHouse(w http.ResponseWriter, r *http.Request) { | |
| 515 | + Uuid := 0 | |
| 516 | + if len(r.Header) > 0 { | |
| 517 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 518 | + } | |
| 519 | + if Uuid == 0 { | |
| 520 | + SetHeader(w) | |
| 521 | + return | |
| 522 | + } | |
| 523 | + result, _ := ioutil.ReadAll(r.Body) | |
| 524 | + r.Body.Close() | |
| 525 | + | |
| 526 | + s := string(result) | |
| 527 | + logger.Info("PutCattoWareHouse , body:%v,uuid=%v", s, Uuid) | |
| 528 | + | |
| 529 | + //HandlerPutCattoWareHouse(w, s, Uuid) | |
| 530 | +} | |
| 531 | + | |
| 532 | +func TakeCatoutfromWareHouse(w http.ResponseWriter, r *http.Request) { | |
| 533 | + Uuid := 0 | |
| 534 | + if len(r.Header) > 0 { | |
| 535 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 536 | + } | |
| 537 | + if Uuid == 0 { | |
| 538 | + SetHeader(w) | |
| 539 | + return | |
| 540 | + } | |
| 541 | + result, _ := ioutil.ReadAll(r.Body) | |
| 542 | + r.Body.Close() | |
| 543 | + | |
| 544 | + s := string(result) | |
| 545 | + logger.Info("TakeCatoutfromWareHouse , body:%v,uuid=%v", s, Uuid) | |
| 546 | + | |
| 547 | + ///HandlerTakeCatoutfromWareHouse(w, s, Uuid) | |
| 548 | +} | |
| 549 | + | |
| 550 | +func DoBuyCat(w http.ResponseWriter, r *http.Request) { | |
| 551 | + Uuid := 0 | |
| 552 | + if len(r.Header) > 0 { | |
| 553 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 554 | + } | |
| 555 | + if Uuid == 0 { | |
| 556 | + SetHeader(w) | |
| 557 | + return | |
| 558 | + } | |
| 559 | + result, _ := ioutil.ReadAll(r.Body) | |
| 560 | + r.Body.Close() | |
| 561 | + | |
| 562 | + s := string(result) | |
| 563 | + logger.Info("DoBuyCat , body:%v,uuid=%v", s, Uuid) | |
| 564 | + | |
| 565 | + //HandlerDoBuyCat(w, s, Uuid) | |
| 566 | +} | |
| 567 | + | |
| 568 | +func QueryBuyCat(w http.ResponseWriter, r *http.Request) { | |
| 569 | + Uuid := 0 | |
| 570 | + if len(r.Header) > 0 { | |
| 571 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 572 | + } | |
| 573 | + if Uuid == 0 { | |
| 574 | + SetHeader(w) | |
| 575 | + return | |
| 576 | + } | |
| 577 | + result, _ := ioutil.ReadAll(r.Body) | |
| 578 | + r.Body.Close() | |
| 579 | + | |
| 580 | + s := string(result) | |
| 581 | + logger.Info("QueryBuyCat , body:%v,uuid=%v", s, Uuid) | |
| 582 | + | |
| 583 | + //HandlerQueryBuyCat(w, s, Uuid) | |
| 584 | +} | |
| 585 | + | |
| 586 | +func ClickRandGift(w http.ResponseWriter, r *http.Request) { | |
| 587 | + Uuid := 0 | |
| 588 | + if len(r.Header) > 0 { | |
| 589 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 590 | + } | |
| 591 | + if Uuid == 0 { | |
| 592 | + SetHeader(w) | |
| 593 | + return | |
| 594 | + } | |
| 595 | + result, _ := ioutil.ReadAll(r.Body) | |
| 596 | + r.Body.Close() | |
| 597 | + | |
| 598 | + s := string(result) | |
| 599 | + logger.Info("ClickRandGift , body:%v,uuid=%v", s, Uuid) | |
| 600 | + | |
| 601 | + //HandlerClickRandGift(w, s, Uuid) | |
| 602 | +} | |
| 603 | + | |
| 604 | +func AutoMerge(w http.ResponseWriter, r *http.Request) { | |
| 605 | + Uuid := 0 | |
| 606 | + if len(r.Header) > 0 { | |
| 607 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 608 | + } | |
| 609 | + if Uuid == 0 { | |
| 610 | + SetHeader(w) | |
| 611 | + return | |
| 612 | + } | |
| 613 | + result, _ := ioutil.ReadAll(r.Body) | |
| 614 | + r.Body.Close() | |
| 615 | + | |
| 616 | + s := string(result) | |
| 617 | + logger.Info("AutoMerge , body:%v,uuid=%v", s, Uuid) | |
| 618 | + | |
| 619 | +// HandlerAutoMerge(w, s, Uuid) | |
| 620 | +} | |
| 621 | + | |
| 622 | +func ClickCatBox(w http.ResponseWriter, r *http.Request) { | |
| 623 | + Uuid := 0 | |
| 624 | + if len(r.Header) > 0 { | |
| 625 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 626 | + } | |
| 627 | + if Uuid == 0 { | |
| 628 | + SetHeader(w) | |
| 629 | + return | |
| 630 | + } | |
| 631 | + result, _ := ioutil.ReadAll(r.Body) | |
| 632 | + r.Body.Close() | |
| 633 | + | |
| 634 | + s := string(result) | |
| 635 | + logger.Info("ClickCatBox , body:%v,uuid=%v", s, Uuid) | |
| 636 | + | |
| 637 | + //HandlerClickCatBox(w, s, Uuid) | |
| 638 | +} | |
| 639 | + | |
| 640 | +func GenerateBox(w http.ResponseWriter, r *http.Request) { | |
| 641 | + Uuid := 0 | |
| 642 | + if len(r.Header) > 0 { | |
| 643 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 644 | + } | |
| 645 | + if Uuid == 0 { | |
| 646 | + SetHeader(w) | |
| 647 | + return | |
| 648 | + } | |
| 649 | + result, _ := ioutil.ReadAll(r.Body) | |
| 650 | + r.Body.Close() | |
| 651 | + | |
| 652 | + s := string(result) | |
| 653 | + logger.Info("GenerateBox , body:%v,uuid=%v", s, Uuid) | |
| 654 | + | |
| 655 | +// HandlerGenerateBox(w, s, Uuid) | |
| 656 | +} | |
| 657 | + | |
| 658 | +func UpgradeCatBox(w http.ResponseWriter, r *http.Request) { | |
| 659 | + Uuid := 0 | |
| 660 | + if len(r.Header) > 0 { | |
| 661 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 662 | + } | |
| 663 | + if Uuid == 0 { | |
| 664 | + SetHeader(w) | |
| 665 | + return | |
| 666 | + } | |
| 667 | + result, _ := ioutil.ReadAll(r.Body) | |
| 668 | + r.Body.Close() | |
| 669 | + | |
| 670 | + s := string(result) | |
| 671 | + logger.Info("UpgradeCatBox , body:%v,uuid=%v", s, Uuid) | |
| 672 | + | |
| 673 | +// HandlerUpgradeCatBox(w, s, Uuid) | |
| 674 | +} | |
| 675 | + | |
| 676 | +func AcclecteBoxRate(w http.ResponseWriter, r *http.Request) { | |
| 677 | + Uuid := 0 | |
| 678 | + if len(r.Header) > 0 { | |
| 679 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 680 | + } | |
| 681 | + if Uuid == 0 { | |
| 682 | + SetHeader(w) | |
| 683 | + return | |
| 684 | + } | |
| 685 | + result, _ := ioutil.ReadAll(r.Body) | |
| 686 | + r.Body.Close() | |
| 687 | + | |
| 688 | + s := string(result) | |
| 689 | + logger.Info("AcclecteBoxRate , body:%v,uuid=%v", s, Uuid) | |
| 690 | + | |
| 691 | +// HandlerAcclecteBoxRate(w, s, Uuid) | |
| 692 | +} | |
| 693 | + | |
| 694 | +func AcclecteGold(w http.ResponseWriter, r *http.Request) { | |
| 695 | + Uuid := 0 | |
| 696 | + if len(r.Header) > 0 { | |
| 697 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 698 | + } | |
| 699 | + if Uuid == 0 { | |
| 700 | + SetHeader(w) | |
| 701 | + return | |
| 702 | + } | |
| 703 | + result, _ := ioutil.ReadAll(r.Body) | |
| 704 | + r.Body.Close() | |
| 705 | + | |
| 706 | + s := string(result) | |
| 707 | + logger.Info("AcclecteGold , body:%v,uuid=%v", s, Uuid) | |
| 708 | + | |
| 709 | +// HandlerAcclecteGold(w, s, Uuid) | |
| 710 | +} | |
| 711 | + | |
| 712 | +func ExchangePos(w http.ResponseWriter, r *http.Request) { | |
| 713 | + Uuid := 0 | |
| 714 | + if len(r.Header) > 0 { | |
| 715 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 716 | + } | |
| 717 | + if Uuid == 0 { | |
| 718 | + SetHeader(w) | |
| 719 | + return | |
| 720 | + } | |
| 721 | + result, _ := ioutil.ReadAll(r.Body) | |
| 722 | + r.Body.Close() | |
| 723 | + | |
| 724 | + s := string(result) | |
| 725 | + logger.Info("ExchangePos , body:%v,uuid=%v", s, Uuid) | |
| 726 | + | |
| 727 | + //HandlerExchangePos(w, s, Uuid) | |
| 728 | +} | |
| 729 | + | |
| 730 | +func GetUserData(w http.ResponseWriter, r *http.Request) { | |
| 731 | + Uuid := 0 | |
| 732 | + if len(r.Header) > 0 { | |
| 733 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 734 | + } | |
| 735 | + if Uuid == 0 { | |
| 736 | + SetHeader(w) | |
| 737 | + return | |
| 738 | + } | |
| 739 | + result, _ := ioutil.ReadAll(r.Body) | |
| 740 | + r.Body.Close() | |
| 741 | + | |
| 742 | + s := string(result) | |
| 743 | + logger.Info("GetUserData , body:%v,uuid=%v", s, Uuid) | |
| 744 | + | |
| 745 | + //HandlerGetUserData(w, s, Uuid) | |
| 746 | +} | |
| 747 | + | |
| 748 | +func UserLogin(w http.ResponseWriter, r *http.Request) { | |
| 749 | + /*logger.Info("%%%%%%%%%%%%%%%%path=%v", *r.URL) | |
| 750 | + for k, v := range r.Header { | |
| 751 | + logger.Info("*********************key=%v,value=%v", k, v) | |
| 752 | + } | |
| 753 | + | |
| 754 | + w.Header().Add("Access-Control-Allow-Headers", "") | |
| 755 | + w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,X_Requested_With,Accept, Origin, Host, Connection, Accept-Encoding, Accept-Language,DNT, X-CustomHeader, Keep-Alive, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Pragma") | |
| 756 | + */ | |
| 757 | + Uuid := 0 | |
| 758 | + if len(r.Header) > 0 { | |
| 759 | + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid")) | |
| 760 | + } | |
| 761 | + | |
| 762 | + if Uuid == 0 { | |
| 763 | + SetHeader(w) | |
| 764 | + /*var resp UserLoginResp | |
| 765 | + resp.Code = -1 | |
| 766 | + resp.Message = "uuid is nil" | |
| 767 | + respstr, _ := json.Marshal(&resp) | |
| 768 | + fmt.Fprint(w, string(respstr)) | |
| 769 | + logger.Error("UserLogin uuid=0! failed!")*/ | |
| 770 | + return | |
| 771 | + } | |
| 772 | + result, _ := ioutil.ReadAll(r.Body) | |
| 773 | + r.Body.Close() | |
| 774 | + | |
| 775 | + s := string(result) | |
| 776 | + logger.Info("UserLogin , body:%v,uuid=%v", s, Uuid) | |
| 777 | + | |
| 778 | +// HandlerLogin(w, s, Uuid) | |
| 779 | +} | |
| 780 | + | |
| 781 | +func ReviewAllianceHandler(w http.ResponseWriter, r *http.Request) { | |
| 782 | + //defer utils.PrintPanicStack() | |
| 783 | + | |
| 784 | + result, _ := ioutil.ReadAll(r.Body) | |
| 785 | + r.Body.Close() | |
| 786 | + | |
| 787 | + s := string(result) | |
| 788 | + logger.Info("ReviewAllianceHandler , body:%v", s) | |
| 789 | + | |
| 790 | + //go HandleReviewedAlliance(s) | |
| 791 | + fmt.Fprint(w, "Success!") | |
| 792 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,23 @@ |
| 1 | +package logic | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "common/beegomap" | |
| 5 | + "net/http" | |
| 6 | + "sync" | |
| 7 | +) | |
| 8 | + | |
| 9 | +var ( | |
| 10 | + m_userInfo *beegomap.BeeMap //make(map[int32]*UserData | |
| 11 | + Maplock *sync.RWMutex | |
| 12 | +) | |
| 13 | + | |
| 14 | +func init() { | |
| 15 | + m_userInfo = beegomap.NewBeeMap() | |
| 16 | + Maplock = new(sync.RWMutex) | |
| 17 | +} | |
| 18 | + | |
| 19 | +func SetHeader(w http.ResponseWriter) { | |
| 20 | + w.Header().Set("Access-Control-Allow-Origin", "*") //允许访问所有域 | |
| 21 | + w.Header().Set("Content-Type", "application/json") | |
| 22 | + w.Header().Set("Access-Control-Allow-Headers", "Content-Type,Uuid") | |
| 23 | +} | |
| 0 | 24 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,80 @@ |
| 1 | +package main | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "HttpServer/conf" | |
| 5 | + "HttpServer/jsonconf" | |
| 6 | + "HttpServer/logic" | |
| 7 | + "HttpServer/redishandler" | |
| 8 | + "common/logger" | |
| 9 | + "flag" | |
| 10 | + "os" | |
| 11 | + "runtime/pprof" | |
| 12 | + "time" | |
| 13 | +) | |
| 14 | + | |
| 15 | +func InitLogger(file string, lvl int) { | |
| 16 | + logger.New(file, lvl, logger.Rotate{Size: logger.GB, Expired: time.Hour * 24 * 7, Interval: time.Hour * 24}) | |
| 17 | +} | |
| 18 | + | |
| 19 | +func main() { | |
| 20 | + //defer utils.PrintPanicStack() | |
| 21 | + filename := flag.String("conf_path", "./conf/world.xml", "config file path") | |
| 22 | + logpath := flag.String("logpath", "./log", "log file path") | |
| 23 | + cpuprofile := flag.String("cpuprofile", "", "write cpu profile to file") | |
| 24 | + secs := flag.Int("exitsecs", 0, "how many secs to exit, defaut 0 to never exit") | |
| 25 | + lvl := flag.Int("lvl", 6, "log level default -debug") | |
| 26 | + flag.Parse() | |
| 27 | + InitLogger(*logpath+"/world.log", *lvl) | |
| 28 | + logger.Info("config file path:%v", *filename) | |
| 29 | + | |
| 30 | + if *cpuprofile != "" { | |
| 31 | + f, err := os.Create(*cpuprofile) | |
| 32 | + if err != nil { | |
| 33 | + logger.Info("open cpu profile error %v:", err) | |
| 34 | + } else { | |
| 35 | + pprof.StartCPUProfile(f) | |
| 36 | + defer pprof.StopCPUProfile() | |
| 37 | + } | |
| 38 | + } | |
| 39 | + var ch chan int = make(chan int) | |
| 40 | + go waitFor(ch, *secs) | |
| 41 | + | |
| 42 | + err := conf.LoadConf(*filename) | |
| 43 | + if err != nil { | |
| 44 | + logger.Info("error load conf fail %v:", err) | |
| 45 | + return | |
| 46 | + } | |
| 47 | + | |
| 48 | + err = jsonconf.LoadJsonConf() | |
| 49 | + if err != nil { | |
| 50 | + logger.Info("error load LoadJsonConf fail %v:", err) | |
| 51 | + return | |
| 52 | + } | |
| 53 | + | |
| 54 | + err = redishandler.Init() | |
| 55 | + if err != nil { | |
| 56 | + logger.Info("err init redis err=%v", err) | |
| 57 | + return | |
| 58 | + } | |
| 59 | + | |
| 60 | + go logic.StartHttpServe() | |
| 61 | + //go logic.StartHttpTicker() | |
| 62 | + time.Sleep(time.Duration(2) * time.Second) | |
| 63 | + logic.Testsendhttp() | |
| 64 | + | |
| 65 | + select { | |
| 66 | + case _ = <-ch: | |
| 67 | + logger.Info("---I'm done----") | |
| 68 | + break | |
| 69 | + } | |
| 70 | +} | |
| 71 | + | |
| 72 | +func waitFor(ch chan int, secs int) { | |
| 73 | + if secs <= 0 { | |
| 74 | + return | |
| 75 | + } | |
| 76 | + time.Sleep(time.Duration(secs) * time.Second) | |
| 77 | + | |
| 78 | + logger.Info("-------------ready to exit--------------------") | |
| 79 | + ch <- 1 | |
| 80 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,125 @@ |
| 1 | +package redishandler | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "HttpServer/conf" | |
| 5 | + "common/logger" | |
| 6 | + "common/redis" | |
| 7 | + "encoding/json" | |
| 8 | + "errors" | |
| 9 | + "strconv" | |
| 10 | +) | |
| 11 | + | |
| 12 | +const USER_TOKEN_KEY = "login_token" | |
| 13 | + | |
| 14 | +var gRedis *redis.RedisClient = nil | |
| 15 | + | |
| 16 | +func GetRedisClient() *redis.RedisClient { | |
| 17 | + return gRedis | |
| 18 | +} | |
| 19 | + | |
| 20 | +type TestInfo struct { | |
| 21 | + Gold int64 //金币 | |
| 22 | + Head string //头像地址 | |
| 23 | + NickName string //昵称 | |
| 24 | + Highestlv int //当前最高猫等级 | |
| 25 | + Uuid int //uuid | |
| 26 | +} | |
| 27 | + | |
| 28 | +type Mytest struct { | |
| 29 | + Test1 int | |
| 30 | + M_userInfo map[int]int | |
| 31 | +} | |
| 32 | + | |
| 33 | +func TestMyredis() { | |
| 34 | + //testmap | |
| 35 | + var test Mytest | |
| 36 | + test.Test1 = 1 | |
| 37 | + test.M_userInfo = make(map[int]int) | |
| 38 | + test.M_userInfo[1] = 1 | |
| 39 | + test.M_userInfo[1] = 2 | |
| 40 | + test.M_userInfo[2] = 2 | |
| 41 | + ddt, _ := json.Marshal(&test) | |
| 42 | + gRedis.SetString("testddt", string(ddt)) | |
| 43 | + logger.Info("testddt test=%v.ddt=%v", test, string(ddt)) | |
| 44 | + | |
| 45 | + rest, err := gRedis.GetString("testddt") | |
| 46 | + if err != nil { | |
| 47 | + logger.Error("TestMyredis ddtfailed err=%v", err) | |
| 48 | + } else { | |
| 49 | + var restest Mytest | |
| 50 | + err = json.Unmarshal([]byte(rest), &restest) | |
| 51 | + logger.Info("testddt rest=%v,restest=%v", rest, restest) | |
| 52 | + | |
| 53 | + } | |
| 54 | + //gRedis.HSet("luheng", "luhengsb", "22") | |
| 55 | + //gRedis.HSet("luheng", "luhengbb", "33") | |
| 56 | + | |
| 57 | + var rinfo TestInfo | |
| 58 | + rinfo.Gold = 100 | |
| 59 | + rinfo.Highestlv = 99 | |
| 60 | + rinfo.Head = "sb" | |
| 61 | + rinfo.NickName = "sd" | |
| 62 | + rinfo.Uuid = 1004 | |
| 63 | + rinfostr, _ := json.Marshal(&rinfo) | |
| 64 | + err = gRedis.Zadd("luhengtest", float64(rinfo.Gold), string(rinfostr)) | |
| 65 | + if err != nil { | |
| 66 | + logger.Info("TestMyredis failed err=%v", err) | |
| 67 | + } else { | |
| 68 | + logger.Info("TestMyredis success rinfostr=%v", string(rinfostr)) | |
| 69 | + } | |
| 70 | + rinfo.Gold = 1000 | |
| 71 | + rinfo.Highestlv = 99 | |
| 72 | + rinfo.Head = "sb1" | |
| 73 | + rinfo.NickName = "sd1" | |
| 74 | + rinfo.Uuid = 1005 | |
| 75 | + rinfostr, _ = json.Marshal(&rinfo) | |
| 76 | + err = gRedis.Zadd("luhengtest", float64(rinfo.Gold), string(rinfostr)) | |
| 77 | + rinfo.Gold = 10000 | |
| 78 | + rinfo.Highestlv = 99 | |
| 79 | + rinfo.Head = "sb2" | |
| 80 | + rinfo.NickName = "sd2" | |
| 81 | + rinfo.Uuid = 1006 | |
| 82 | + rinfostr, _ = json.Marshal(&rinfo) | |
| 83 | + err = gRedis.Zadd("luhengtest", float64(rinfo.Gold), string(rinfostr)) | |
| 84 | + | |
| 85 | + vv, err := gRedis.ZRevRangewithIndex("luhengtest", 0, -1) | |
| 86 | + if err == nil { | |
| 87 | + for k, v := range vv { | |
| 88 | + rinfobyte, _ := v.([]byte) | |
| 89 | + var rinfo TestInfo | |
| 90 | + //logger.Info("TestMyredis ori=%v", string(v.([]byte))) | |
| 91 | + err := json.Unmarshal(rinfobyte, &rinfo) | |
| 92 | + if err == nil { | |
| 93 | + logger.Info("TestMyredis k=%v,v=%v", k, rinfo) | |
| 94 | + } else { | |
| 95 | + logger.Info("TestMyredis isbigsb err=%v", err) | |
| 96 | + } | |
| 97 | + | |
| 98 | + } | |
| 99 | + } else { | |
| 100 | + logger.Info("test is err=%v", err) | |
| 101 | + } | |
| 102 | +} | |
| 103 | + | |
| 104 | +func Init() error { | |
| 105 | + redis_cfg := conf.GetRedisConf() | |
| 106 | + if gRedis = redis.NewRedisClient(redis_cfg.Host, redis_cfg.Password, redis_cfg.Db); gRedis == nil { | |
| 107 | + return errors.New("initRedis error") | |
| 108 | + } | |
| 109 | + | |
| 110 | + logger.Info("init redis success!") | |
| 111 | + TestMyredis() | |
| 112 | + return nil | |
| 113 | +} | |
| 114 | + | |
| 115 | +/* | |
| 116 | + 加载token | |
| 117 | +*/ | |
| 118 | +func LoadPlayerTokenFromRedis(uid int) (string, error) { | |
| 119 | + token, err := gRedis.HGet(USER_TOKEN_KEY, strconv.Itoa(uid)) | |
| 120 | + if err != nil { | |
| 121 | + logger.Notic("LoadPlayerTokenFromRedis failed uid:%v err%v", uid, err) | |
| 122 | + return "", err | |
| 123 | + } | |
| 124 | + return token, err | |
| 125 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,125 @@ |
| 1 | +// Copyright 2014 beego Author. All Rights Reserved. | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | +// you may not use this file except in compliance with the License. | |
| 5 | +// You may obtain a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | +// See the License for the specific language governing permissions and | |
| 13 | +// limitations under the License. | |
| 14 | + | |
| 15 | +// source code is here : https://github.com/astaxie/beego/blob/master/utils/safemap.go | |
| 16 | + | |
| 17 | +//package utils | |
| 18 | +package beegomap | |
| 19 | + | |
| 20 | +import ( | |
| 21 | + "sync" | |
| 22 | +) | |
| 23 | + | |
| 24 | +// BeeMap is a map with lock | |
| 25 | +type BeeMap struct { | |
| 26 | + lock *sync.RWMutex | |
| 27 | + Bm map[uint32]interface{} | |
| 28 | +} | |
| 29 | + | |
| 30 | +// NewBeeMap return new safemap | |
| 31 | +func NewBeeMap() *BeeMap { | |
| 32 | + return &BeeMap{ | |
| 33 | + lock: new(sync.RWMutex), | |
| 34 | + Bm: make(map[uint32]interface{}), | |
| 35 | + } | |
| 36 | +} | |
| 37 | + | |
| 38 | +// Get from maps return the k's value | |
| 39 | +func (m *BeeMap) Get(k uint32) interface{} { | |
| 40 | + m.lock.RLock() | |
| 41 | + defer m.lock.RUnlock() | |
| 42 | + if val, ok := m.Bm[k]; ok { | |
| 43 | + return val | |
| 44 | + } | |
| 45 | + return nil | |
| 46 | +} | |
| 47 | + | |
| 48 | +// Set Maps the given key and value. Returns false | |
| 49 | +// if the key is already in the map and changes nothing. | |
| 50 | +func (m *BeeMap) Set(k uint32, v interface{}) bool { | |
| 51 | + m.lock.Lock() | |
| 52 | + defer m.lock.Unlock() | |
| 53 | + if val, ok := m.Bm[k]; !ok { | |
| 54 | + m.Bm[k] = v | |
| 55 | + } else if val != v { | |
| 56 | + m.Bm[k] = v | |
| 57 | + } else { | |
| 58 | + return false | |
| 59 | + } | |
| 60 | + return true | |
| 61 | +} | |
| 62 | + | |
| 63 | +//force set | |
| 64 | +func (m *BeeMap) Replace(k uint32, v interface{}) bool { | |
| 65 | + m.lock.Lock() | |
| 66 | + defer m.lock.Unlock() | |
| 67 | + delete(m.Bm, k) | |
| 68 | + if val, ok := m.Bm[k]; !ok { | |
| 69 | + m.Bm[k] = v | |
| 70 | + } else if val != v { | |
| 71 | + m.Bm[k] = v | |
| 72 | + } else { | |
| 73 | + return false | |
| 74 | + } | |
| 75 | + return true | |
| 76 | +} | |
| 77 | + | |
| 78 | +// Check Returns true if k is exist in the map. | |
| 79 | +func (m *BeeMap) Check(k uint32) bool { | |
| 80 | + m.lock.RLock() | |
| 81 | + defer m.lock.RUnlock() | |
| 82 | + if _, ok := m.Bm[k]; !ok { | |
| 83 | + return false | |
| 84 | + } | |
| 85 | + return true | |
| 86 | +} | |
| 87 | + | |
| 88 | +// Delete the given key and value. | |
| 89 | +func (m *BeeMap) Delete(k uint32) { | |
| 90 | + m.lock.Lock() | |
| 91 | + defer m.lock.Unlock() | |
| 92 | + delete(m.Bm, k) | |
| 93 | +} | |
| 94 | + | |
| 95 | +// Items returns all items in safemap. | |
| 96 | +func (m *BeeMap) Items() map[uint32]interface{} { | |
| 97 | + m.lock.RLock() | |
| 98 | + defer m.lock.RUnlock() | |
| 99 | + r := make(map[uint32]interface{}) | |
| 100 | + for k, v := range m.Bm { | |
| 101 | + r[k] = v | |
| 102 | + } | |
| 103 | + return r | |
| 104 | +} | |
| 105 | + | |
| 106 | +func (m *BeeMap) Len() uint32 { | |
| 107 | + m.lock.RLock() | |
| 108 | + defer m.lock.RUnlock() | |
| 109 | + return uint32(len(m.Bm)) | |
| 110 | +} | |
| 111 | + | |
| 112 | +func (m *BeeMap) Clear() { | |
| 113 | + items := m.Items() | |
| 114 | + for k := range items { | |
| 115 | + m.Delete(k) | |
| 116 | + } | |
| 117 | +} | |
| 118 | +func (m *BeeMap) GetRandomKey() uint32 { | |
| 119 | + m.lock.RLock() | |
| 120 | + defer m.lock.RUnlock() | |
| 121 | + for k := range m.Items() { | |
| 122 | + return k | |
| 123 | + } | |
| 124 | + return 0 | |
| 125 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,280 @@ |
| 1 | +package logger | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "fmt" | |
| 5 | + "log" | |
| 6 | + "os" | |
| 7 | + "path/filepath" | |
| 8 | + "strconv" | |
| 9 | + "strings" | |
| 10 | + "sync" | |
| 11 | + "sync/atomic" | |
| 12 | + "time" | |
| 13 | +) | |
| 14 | + | |
| 15 | +type ByteSize float64 | |
| 16 | + | |
| 17 | +const ( | |
| 18 | + _ = iota | |
| 19 | + KB ByteSize = 1 << (10 * iota) | |
| 20 | + MB | |
| 21 | + GB | |
| 22 | + TB | |
| 23 | + PB | |
| 24 | + EB | |
| 25 | + ZB | |
| 26 | + YB | |
| 27 | +) | |
| 28 | + | |
| 29 | +const ( | |
| 30 | + checkSize = 2 * time.Minute | |
| 31 | + checkExpired = 2 * time.Hour | |
| 32 | +) | |
| 33 | + | |
| 34 | +var ( | |
| 35 | + stdFatal = log.New(os.Stderr, "\033[0;33mFATAL:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 36 | + stdError = log.New(os.Stderr, "\033[0;31mERROR:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 37 | + stdWarn = log.New(os.Stderr, "\033[0;35mWARN:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 38 | + ll *Logger | |
| 39 | +) | |
| 40 | + | |
| 41 | +type Logger struct { | |
| 42 | + errCount int32 | |
| 43 | + rotate Rotate | |
| 44 | + level chan int | |
| 45 | + | |
| 46 | + rwm sync.RWMutex | |
| 47 | + file *os.File | |
| 48 | + debug, info, notic, warn, err, fatal *log.Logger | |
| 49 | +} | |
| 50 | + | |
| 51 | +type Rotate struct { | |
| 52 | + Size ByteSize | |
| 53 | + Expired, Interval time.Duration | |
| 54 | +} | |
| 55 | + | |
| 56 | +func New(fp string, lvl int, rotate Rotate) *Logger { | |
| 57 | + f, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) | |
| 58 | + if err != nil { | |
| 59 | + stdFatal.Fatalln(err.Error()) | |
| 60 | + } | |
| 61 | + ll = new(Logger) | |
| 62 | + ll.rotate = rotate | |
| 63 | + ll.file = f | |
| 64 | + ll.level = make(chan int) | |
| 65 | + go ll.loop() | |
| 66 | + ll.SetLevel(lvl) | |
| 67 | + return ll | |
| 68 | +} | |
| 69 | + | |
| 70 | +func (l *Logger) SetLevel(lvl int) { | |
| 71 | + l.level <- lvl | |
| 72 | +} | |
| 73 | + | |
| 74 | +func (l *Logger) setLevel(f *os.File, lvl int) { | |
| 75 | + switch { | |
| 76 | + case lvl > 5: | |
| 77 | + l.debug = log.New(f, "\033[0;36mDEBUG:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 78 | + fallthrough | |
| 79 | + case lvl > 4: | |
| 80 | + l.info = log.New(f, "INFO: ", log.LstdFlags|log.Lshortfile) | |
| 81 | + fallthrough | |
| 82 | + case lvl > 3: | |
| 83 | + l.notic = log.New(f, "\033[0;32mNOTIC:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 84 | + fallthrough | |
| 85 | + case lvl > 2: | |
| 86 | + l.warn = log.New(f, "\033[0;35mWARN:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 87 | + fallthrough | |
| 88 | + case lvl > 1: | |
| 89 | + l.err = log.New(f, "\033[0;31mERROR:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 90 | + fallthrough | |
| 91 | + case lvl > 0: | |
| 92 | + l.fatal = log.New(f, "\033[0;33mFATAL:\033[0m ", log.LstdFlags|log.Lshortfile) | |
| 93 | + } | |
| 94 | + switch { | |
| 95 | + case lvl < 1: | |
| 96 | + l.fatal = nil | |
| 97 | + fallthrough | |
| 98 | + case lvl < 2: | |
| 99 | + l.err = nil | |
| 100 | + fallthrough | |
| 101 | + case lvl < 3: | |
| 102 | + l.warn = nil | |
| 103 | + fallthrough | |
| 104 | + case lvl < 4: | |
| 105 | + l.notic = nil | |
| 106 | + fallthrough | |
| 107 | + case lvl < 5: | |
| 108 | + l.info = nil | |
| 109 | + fallthrough | |
| 110 | + case lvl < 6: | |
| 111 | + l.debug = nil | |
| 112 | + } | |
| 113 | +} | |
| 114 | + | |
| 115 | +func (l *Logger) getFileSize() ByteSize { | |
| 116 | + l.rwm.RLock() | |
| 117 | + defer l.rwm.RUnlock() | |
| 118 | + fi, err := l.file.Stat() | |
| 119 | + if err != nil { | |
| 120 | + Warn("get log file size failed, no trunc %s", err.Error()) | |
| 121 | + return 0.0 | |
| 122 | + } | |
| 123 | + return ByteSize(fi.Size()) | |
| 124 | +} | |
| 125 | + | |
| 126 | +func (l *Logger) trunc(fp, ext string, lvl int) { | |
| 127 | + l.rwm.Lock() | |
| 128 | + defer l.rwm.Unlock() | |
| 129 | + err := l.file.Close() | |
| 130 | + if err != nil { | |
| 131 | + stdWarn.Println("fail to close log file", err.Error()) | |
| 132 | + return | |
| 133 | + } | |
| 134 | + err = os.Rename(fp, fp+ext) | |
| 135 | + if err != nil { | |
| 136 | + stdWarn.Println("fail to rename log file, no trunc", err.Error()) | |
| 137 | + } | |
| 138 | + f, err := os.OpenFile(fp, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) | |
| 139 | + if err != nil { | |
| 140 | + Error("create log file failed %s", err.Error()) | |
| 141 | + return | |
| 142 | + } | |
| 143 | + l.setLevel(f, lvl) | |
| 144 | + l.file = f | |
| 145 | +} | |
| 146 | + | |
| 147 | +func suffix(t time.Time) string { | |
| 148 | + y, m, d := t.Date() | |
| 149 | + return "-" + fmt.Sprintf("%04d%02d%02d%02d", y, m, d, t.Hour()) | |
| 150 | +} | |
| 151 | + | |
| 152 | +func toNextBound(d time.Duration) time.Duration { | |
| 153 | + return time.Now().Truncate(d).Add(d).Sub(time.Now()) | |
| 154 | +} | |
| 155 | + | |
| 156 | +func (l *Logger) loop() error { | |
| 157 | + interval := time.After(toNextBound(l.rotate.Interval)) | |
| 158 | + expired := time.After(checkExpired) | |
| 159 | + var sizeExt, lvl int = 1, 4 | |
| 160 | + fp, err := filepath.Abs(l.file.Name()) | |
| 161 | + if err != nil { | |
| 162 | + stdFatal.Fatalln("get log filepath failed %s", err.Error()) | |
| 163 | + } | |
| 164 | + for { | |
| 165 | + var size <-chan time.Time | |
| 166 | + if toNextBound(l.rotate.Interval) != checkSize { | |
| 167 | + size = time.After(checkSize) | |
| 168 | + } | |
| 169 | + select { | |
| 170 | + case lvl = <-l.level: | |
| 171 | + l.rwm.Lock() | |
| 172 | + l.setLevel(l.file, lvl) | |
| 173 | + l.rwm.Unlock() | |
| 174 | + Notic("log level change to %d", lvl) | |
| 175 | + case t := <-interval: | |
| 176 | + interval = time.After(l.rotate.Interval) | |
| 177 | + l.trunc(fp, suffix(t), lvl) | |
| 178 | + sizeExt = 1 | |
| 179 | + Notic("log truncated by time interval") | |
| 180 | + case <-expired: | |
| 181 | + expired = time.After(checkExpired) | |
| 182 | + err := filepath.Walk(filepath.Dir(fp), | |
| 183 | + func(path string, info os.FileInfo, err error) error { | |
| 184 | + if err != nil { | |
| 185 | + return nil | |
| 186 | + } | |
| 187 | + isLog := strings.Contains(info.Name(), ".log") | |
| 188 | + | |
| 189 | + //log.Println("strings.Contains(", info.Name(), " log') isLog = ", isLog) | |
| 190 | + if time.Since(info.ModTime()) > l.rotate.Expired && isLog && info.IsDir() == false { | |
| 191 | + if err := os.Remove(path); err != nil { | |
| 192 | + return err | |
| 193 | + } | |
| 194 | + Notic("remove expired log files %s", filepath.Base(path)) | |
| 195 | + } | |
| 196 | + return nil | |
| 197 | + }) | |
| 198 | + if err != nil { | |
| 199 | + Warn("remove expired logs failed %s", err.Error()) | |
| 200 | + } | |
| 201 | + case t := <-size: | |
| 202 | + if l.getFileSize() < l.rotate.Size { | |
| 203 | + break | |
| 204 | + } | |
| 205 | + l.trunc(fp, suffix(t)+"."+strconv.Itoa(sizeExt), lvl) | |
| 206 | + sizeExt++ | |
| 207 | + Notic("log over size, truncated") | |
| 208 | + } | |
| 209 | + } | |
| 210 | +} | |
| 211 | + | |
| 212 | +// Debug log debug message with cyan color. | |
| 213 | +func Debug(format string, v ...interface{}) { | |
| 214 | + ll.rwm.RLock() | |
| 215 | + if ll.debug != nil { | |
| 216 | + ll.debug.Output(2, fmt.Sprintf(format, v...)) | |
| 217 | + } | |
| 218 | + ll.rwm.RUnlock() | |
| 219 | +} | |
| 220 | + | |
| 221 | +// Info log normal message. | |
| 222 | +func Info(format string, v ...interface{}) { | |
| 223 | + ll.rwm.RLock() | |
| 224 | + if ll.info != nil { | |
| 225 | + ll.info.Output(2, fmt.Sprintf(format, v...)) | |
| 226 | + } | |
| 227 | + ll.rwm.RUnlock() | |
| 228 | +} | |
| 229 | + | |
| 230 | +// Notice log notice message with blue color. | |
| 231 | +func Notic(format string, v ...interface{}) { | |
| 232 | + ll.rwm.RLock() | |
| 233 | + if ll.notic != nil { | |
| 234 | + ll.notic.Output(2, fmt.Sprintf(format, v...)) | |
| 235 | + } | |
| 236 | + ll.rwm.RUnlock() | |
| 237 | +} | |
| 238 | + | |
| 239 | +// Error log error message with red color. | |
| 240 | +func Error(format string, v ...interface{}) { | |
| 241 | + atomic.AddInt32(&ll.errCount, 1) | |
| 242 | + stdError.Output(2, fmt.Sprintf(format, v...)) | |
| 243 | + ll.rwm.RLock() | |
| 244 | + if ll.err != nil { | |
| 245 | + ll.err.Output(2, fmt.Sprintf(format, v...)) | |
| 246 | + } | |
| 247 | + ll.rwm.RUnlock() | |
| 248 | +} | |
| 249 | + | |
| 250 | +func ErrCount() int32 { | |
| 251 | + ec := atomic.LoadInt32(&ll.errCount) | |
| 252 | + if ec < 0 { | |
| 253 | + Warn("error count overflow") | |
| 254 | + return -1 | |
| 255 | + } | |
| 256 | + return ec | |
| 257 | +} | |
| 258 | + | |
| 259 | +func Fatal(format string, v ...interface{}) { | |
| 260 | + stdFatal.Output(2, fmt.Sprintf(format, v...)) | |
| 261 | + ll.rwm.RLock() | |
| 262 | + if ll.fatal != nil { | |
| 263 | + ll.fatal.Output(2, fmt.Sprintf(format, v...)) | |
| 264 | + } | |
| 265 | + ll.rwm.RUnlock() | |
| 266 | + os.Exit(1) | |
| 267 | +} | |
| 268 | + | |
| 269 | +func Warn(format string, v ...interface{}) { | |
| 270 | + stdWarn.Output(2, fmt.Sprintf(format, v...)) | |
| 271 | + ll.rwm.RLock() | |
| 272 | + if ll.warn != nil { | |
| 273 | + ll.warn.Output(2, fmt.Sprintf(format, v...)) | |
| 274 | + } | |
| 275 | + ll.rwm.RUnlock() | |
| 276 | +} | |
| 277 | + | |
| 278 | +func GetLogger() *Logger { | |
| 279 | + return ll | |
| 280 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,13 @@ |
| 1 | +package redis | |
| 2 | + | |
| 3 | +const ( | |
| 4 | + USER_LAST_LOGIN_TIME = "CATSERVER_USER_LAST_LOGIN_TIME" //玩家上次登陆时间 | |
| 5 | + USER_INFO = "CATSERVER_USER_USER_INFO" //玩家数据 | |
| 6 | + USER_LAST_CALC_TIME = "CATSERVER_USER_LAST_CALC_TIME" //玩家上一次数据计算时间 | |
| 7 | + USER_INVITE_ID = "CATSERVER_USER_INVITE_ID" //玩家邀请者ID | |
| 8 | + USER_STARTDOUBLE_TIME = "CATSERVER_USER_STARTDOUBLE_TIME" //开启双倍时间 | |
| 9 | + USER_STARTAUTO_TIME = "CATSERVER_USER_STARTAUTO_TIME" //开启自动合成时间 | |
| 10 | + USER_STARTACC_BOX_TIME = "CATSERVER_USER_STARTACC_BOX_TIME" //开启加速成产箱子 | |
| 11 | + USER_WAREHOUSE_INFO = "CATSERVER_USER_WAREHOUSE_INFO" //玩家仓库信息 | |
| 12 | + USER_GOLD_RANK = "CATSERVER_USER_GOLD_RANK" //玩家排行榜,根据金币排 | |
| 13 | +) | ... | ... |
| ... | ... | @@ -0,0 +1,212 @@ |
| 1 | +package redis | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "sync" | |
| 5 | + | |
| 6 | + "github.com/garyburd/redigo/redis" | |
| 7 | + //"math/rand" | |
| 8 | + "errors" | |
| 9 | + //"sync" | |
| 10 | + "fmt" | |
| 11 | + "time" | |
| 12 | +) | |
| 13 | + | |
| 14 | +type subCallBack interface { | |
| 15 | + OnMessage(channnel string, msg []byte) | |
| 16 | + OnPMessage(pattern string, channel string, msg []byte) | |
| 17 | + OnSubscription(kind string, channel string, count int) | |
| 18 | + OnError(err error) | |
| 19 | +} | |
| 20 | + | |
| 21 | +type PubSubClient struct { | |
| 22 | + m_host string | |
| 23 | + db_index uint32 | |
| 24 | + password string | |
| 25 | + m_pool *redis.Pool | |
| 26 | + m_conn redis.PubSubConn | |
| 27 | + m_lock sync.Mutex | |
| 28 | + m_subCallback subCallBack | |
| 29 | + m_subList map[string]bool | |
| 30 | + m_psubList map[string]bool | |
| 31 | +} | |
| 32 | + | |
| 33 | +func NewPubSubClient(host string, password string, db uint32) *PubSubClient { | |
| 34 | + client := new(PubSubClient) | |
| 35 | + client.m_host = host | |
| 36 | + client.db_index = db | |
| 37 | + client.password = password | |
| 38 | + client.m_subList = make(map[string]bool) | |
| 39 | + client.m_psubList = make(map[string]bool) | |
| 40 | + client.m_pool = &redis.Pool{ | |
| 41 | + MaxIdle: 64, | |
| 42 | + //MaxActive: 1, | |
| 43 | + IdleTimeout: 60 * time.Second, | |
| 44 | + TestOnBorrow: func(c redis.Conn, t time.Time) error { | |
| 45 | + _, err := c.Do("PING") | |
| 46 | + | |
| 47 | + return err | |
| 48 | + }, | |
| 49 | + Dial: func() (redis.Conn, error) { | |
| 50 | + c, err := redis.Dial("tcp", host, redis.DialDatabase(int(db)), redis.DialPassword(password)) | |
| 51 | + if err != nil { | |
| 52 | + return nil, err | |
| 53 | + } | |
| 54 | + return c, err | |
| 55 | + }, | |
| 56 | + } | |
| 57 | + | |
| 58 | + conn, err := redis.Dial("tcp", host, redis.DialDatabase(int(db)), redis.DialPassword(password)) | |
| 59 | + if err != nil { | |
| 60 | + fmt.Printf("error dial:%v", err) | |
| 61 | + return nil | |
| 62 | + } | |
| 63 | + client.m_conn = redis.PubSubConn{Conn: conn} | |
| 64 | + return client | |
| 65 | +} | |
| 66 | + | |
| 67 | +func (client *PubSubClient) Publish(channel, value interface{}) error { | |
| 68 | + conn := client.m_pool.Get() | |
| 69 | + defer conn.Close() | |
| 70 | + if _, err := conn.Do("PUBLISH", channel, value); err != nil { | |
| 71 | + return err | |
| 72 | + } | |
| 73 | + return nil | |
| 74 | + | |
| 75 | +} | |
| 76 | + | |
| 77 | +func (client *PubSubClient) Subscribe(channel string) error { | |
| 78 | + client.m_lock.Lock() | |
| 79 | + defer client.m_lock.Unlock() | |
| 80 | + if err := client.m_conn.Subscribe(channel); err != nil { | |
| 81 | + return err | |
| 82 | + } | |
| 83 | + client.m_subList[channel] = true | |
| 84 | + return nil | |
| 85 | +} | |
| 86 | + | |
| 87 | +func (client *PubSubClient) Unsubscribe(channel string) error { | |
| 88 | + //logger.DEBUG("Unsubscribe, channName:%s\n", channel) | |
| 89 | + client.m_lock.Lock() | |
| 90 | + defer client.m_lock.Unlock() | |
| 91 | + if channel == "" { | |
| 92 | + if err := client.m_conn.Unsubscribe(); err != nil { | |
| 93 | + return err | |
| 94 | + } | |
| 95 | + // clear all element in this map | |
| 96 | + for k := range client.m_subList { | |
| 97 | + delete(client.m_subList, k) | |
| 98 | + } | |
| 99 | + } else { | |
| 100 | + if err := client.m_conn.Unsubscribe(channel); err != nil { | |
| 101 | + return err | |
| 102 | + } | |
| 103 | + delete(client.m_subList, channel) | |
| 104 | + } | |
| 105 | + return nil | |
| 106 | +} | |
| 107 | + | |
| 108 | +func (client *PubSubClient) PSubscribe(channel string) error { | |
| 109 | + client.m_lock.Lock() | |
| 110 | + defer client.m_lock.Unlock() | |
| 111 | + if err := client.m_conn.PSubscribe(channel); err != nil { | |
| 112 | + return err | |
| 113 | + } | |
| 114 | + client.m_psubList[channel] = true | |
| 115 | + return nil | |
| 116 | +} | |
| 117 | + | |
| 118 | +func (client *PubSubClient) PUnsubscribe(channel string) error { | |
| 119 | + client.m_lock.Lock() | |
| 120 | + defer client.m_lock.Unlock() | |
| 121 | + if channel == "" { | |
| 122 | + if err := client.m_conn.PUnsubscribe(); err != nil { | |
| 123 | + return err | |
| 124 | + } | |
| 125 | + // clear all element in this map | |
| 126 | + for k := range client.m_subList { | |
| 127 | + delete(client.m_psubList, k) | |
| 128 | + } | |
| 129 | + } else { | |
| 130 | + if err := client.m_conn.PUnsubscribe(channel); err != nil { | |
| 131 | + return err | |
| 132 | + } | |
| 133 | + delete(client.m_subList, channel) | |
| 134 | + } | |
| 135 | + return nil | |
| 136 | +} | |
| 137 | + | |
| 138 | +func (client *PubSubClient) SetSubCallback(cb subCallBack) { | |
| 139 | + client.m_subCallback = cb | |
| 140 | +} | |
| 141 | + | |
| 142 | +func (client *PubSubClient) reConnect() { | |
| 143 | + | |
| 144 | + //1. first close old connection | |
| 145 | + client.m_conn.Close() | |
| 146 | + | |
| 147 | + //2. create a new connection | |
| 148 | + for { | |
| 149 | + // conn, err := redis.Dial("tcp", client.m_host) | |
| 150 | + conn, err := redis.Dial("tcp", client.m_host, redis.DialDatabase(int(client.db_index)), redis.DialPassword(client.password)) | |
| 151 | + if err == nil { | |
| 152 | + client.m_conn = redis.PubSubConn{Conn: conn} | |
| 153 | + return | |
| 154 | + } | |
| 155 | + time.Sleep(time.Second) | |
| 156 | + //logger.DEBUG("reConnecting\n") | |
| 157 | + } | |
| 158 | +} | |
| 159 | + | |
| 160 | +func (client *PubSubClient) reSubscribe() { | |
| 161 | + for key := range client.m_subList { | |
| 162 | + client.Subscribe(key) | |
| 163 | + } | |
| 164 | + for key := range client.m_psubList { | |
| 165 | + client.PSubscribe(key) | |
| 166 | + } | |
| 167 | +} | |
| 168 | + | |
| 169 | +func (client *PubSubClient) StartSubscribe(cb subCallBack) error { | |
| 170 | + if cb == nil { | |
| 171 | + return errors.New("subCallback is not set!") | |
| 172 | + } | |
| 173 | + client.m_subCallback = cb | |
| 174 | + go func() { | |
| 175 | + //exit_loop: | |
| 176 | + for { | |
| 177 | + switch n := client.m_conn.Receive().(type) { | |
| 178 | + case redis.Message: | |
| 179 | + //logger.DEBUG("Message: chann[%s] data[%s]\n", n.Channel, n.Data) | |
| 180 | + if client.m_subCallback.OnMessage != nil { | |
| 181 | + client.m_subCallback.OnMessage(n.Channel, n.Data) | |
| 182 | + } | |
| 183 | + case redis.PMessage: | |
| 184 | + //logger.DEBUG("PMessage: pattern[%s] chann[%s] data[%s]\n", n.Pattern, n.Channel, n.Data) | |
| 185 | + if client.m_subCallback.OnPMessage != nil { | |
| 186 | + client.m_subCallback.OnPMessage(n.Pattern, n.Channel, n.Data) | |
| 187 | + } | |
| 188 | + case redis.Subscription: | |
| 189 | + //logger.DEBUG("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count) | |
| 190 | + if client.m_subCallback.OnSubscription != nil { | |
| 191 | + client.m_subCallback.OnSubscription(n.Kind, n.Channel, n.Count) | |
| 192 | + } | |
| 193 | + if n.Count == 0 { | |
| 194 | + //logger.DEBUG("exit ddd: \n") | |
| 195 | + //break exit_loop | |
| 196 | + } | |
| 197 | + case error: | |
| 198 | + if client.m_subCallback.OnError != nil { | |
| 199 | + client.m_subCallback.OnError(n) | |
| 200 | + } | |
| 201 | + //logger.DEBUG("error: %v\n", n) | |
| 202 | + client.reConnect() | |
| 203 | + client.reSubscribe() | |
| 204 | + //client.m_conn = redis.PubSubConn{Conn: m_psPool.Get()} | |
| 205 | + //break exit_loop | |
| 206 | + } | |
| 207 | + } | |
| 208 | + //logger.DEBUG("exit: \n") | |
| 209 | + }() | |
| 210 | + | |
| 211 | + return nil | |
| 212 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,408 @@ |
| 1 | +package redis | |
| 2 | + | |
| 3 | +import ( | |
| 4 | + "fmt" | |
| 5 | + | |
| 6 | + "github.com/garyburd/redigo/redis" | |
| 7 | + //"math/rand" | |
| 8 | + | |
| 9 | + "strconv" | |
| 10 | + //"sync" | |
| 11 | + "errors" | |
| 12 | + "time" | |
| 13 | +) | |
| 14 | + | |
| 15 | +type RedisClient struct { | |
| 16 | + m_pool *redis.Pool | |
| 17 | +} | |
| 18 | + | |
| 19 | +func NewRedisClient(host string, password string, db_index uint32) *RedisClient { | |
| 20 | + client := new(RedisClient) | |
| 21 | + client.m_pool = &redis.Pool{ | |
| 22 | + MaxIdle: 64, | |
| 23 | + IdleTimeout: 60 * time.Second, | |
| 24 | + TestOnBorrow: func(c redis.Conn, t time.Time) error { | |
| 25 | + _, err := c.Do("PING") | |
| 26 | + | |
| 27 | + return err | |
| 28 | + }, | |
| 29 | + Dial: func() (redis.Conn, error) { | |
| 30 | + c, err := redis.Dial("tcp", host) | |
| 31 | + if err != nil { | |
| 32 | + return nil, err | |
| 33 | + } | |
| 34 | + if password != "" { | |
| 35 | + if _, err := c.Do("AUTH", password); err != nil { | |
| 36 | + c.Close() | |
| 37 | + return nil, err | |
| 38 | + } | |
| 39 | + } | |
| 40 | + _, err = c.Do("SELECT", db_index) | |
| 41 | + if err != nil { | |
| 42 | + return nil, err | |
| 43 | + } | |
| 44 | + | |
| 45 | + return c, err | |
| 46 | + }, | |
| 47 | + } | |
| 48 | + return client | |
| 49 | +} | |
| 50 | + | |
| 51 | +// 写入值永不过期 | |
| 52 | +func (client *RedisClient) SetString(key, value string) error { | |
| 53 | + conn := client.m_pool.Get() | |
| 54 | + defer conn.Close() | |
| 55 | + _, err := conn.Do("SET", key, value) | |
| 56 | + if err != nil { | |
| 57 | + //fmt.Println("redis set failed:", err) | |
| 58 | + } | |
| 59 | + return err | |
| 60 | +} | |
| 61 | + | |
| 62 | +func (client *RedisClient) SetBytes(key string, bys []byte) error { | |
| 63 | + conn := client.m_pool.Get() | |
| 64 | + defer conn.Close() | |
| 65 | + _, err := conn.Do("SET", key, bys) | |
| 66 | + if err != nil { | |
| 67 | + //fmt.Println("redis set failed:", err) | |
| 68 | + } | |
| 69 | + return err | |
| 70 | +} | |
| 71 | + | |
| 72 | +func (client *RedisClient) SetExString(key, value string, expireTime int /* seconds*/) error { | |
| 73 | + conn := client.m_pool.Get() | |
| 74 | + defer conn.Close() | |
| 75 | + expStr := strconv.Itoa(expireTime) | |
| 76 | + _, err := conn.Do("SETEX", key, expStr, value) | |
| 77 | + if err != nil { | |
| 78 | + //fmt.Println("redis set failed:", err) | |
| 79 | + } | |
| 80 | + return err | |
| 81 | +} | |
| 82 | + | |
| 83 | +func (client *RedisClient) Delete(key string) error { | |
| 84 | + conn := client.m_pool.Get() | |
| 85 | + defer conn.Close() | |
| 86 | + _, err := conn.Do("DEL", key) | |
| 87 | + if err != nil { | |
| 88 | + //fmt.Println("redis set failed:", err) | |
| 89 | + } | |
| 90 | + return err | |
| 91 | +} | |
| 92 | + | |
| 93 | +func (client *RedisClient) Expire(key string, expireTime int /* seconds*/) error { | |
| 94 | + conn := client.m_pool.Get() | |
| 95 | + defer conn.Close() | |
| 96 | + expStr := strconv.Itoa(expireTime) | |
| 97 | + _, err := conn.Do("EXPIRE", key, expStr) | |
| 98 | + if err != nil { | |
| 99 | + //fmt.Println("redis set failed:", err) | |
| 100 | + } | |
| 101 | + return err | |
| 102 | +} | |
| 103 | + | |
| 104 | +func (client *RedisClient) GetKeys(pattern string) ([]string, error) { | |
| 105 | + conn := client.m_pool.Get() | |
| 106 | + defer conn.Close() | |
| 107 | + value, err := redis.Strings(conn.Do("keys", pattern)) | |
| 108 | + if err != nil { | |
| 109 | + //fmt.Println("redis get failed:", err) | |
| 110 | + } | |
| 111 | + return value, err | |
| 112 | +} | |
| 113 | + | |
| 114 | +func (client *RedisClient) GetString(key string) (string, error) { | |
| 115 | + conn := client.m_pool.Get() | |
| 116 | + defer conn.Close() | |
| 117 | + value, err := redis.String(conn.Do("GET", key)) | |
| 118 | + if err != nil { | |
| 119 | + //fmt.Println("redis get failed:", err) | |
| 120 | + } | |
| 121 | + return value, err | |
| 122 | +} | |
| 123 | +func (client *RedisClient) GetBytes(key string) ([]byte, error) { | |
| 124 | + conn := client.m_pool.Get() | |
| 125 | + defer conn.Close() | |
| 126 | + value, err := conn.Do("GET", key) | |
| 127 | + if err != nil { | |
| 128 | + fmt.Println("redis get failed:", err) | |
| 129 | + } | |
| 130 | + return value.([]byte), err | |
| 131 | +} | |
| 132 | + | |
| 133 | +func (client *RedisClient) GetInt64(key string) (int64, error) { | |
| 134 | + conn := client.m_pool.Get() | |
| 135 | + defer conn.Close() | |
| 136 | + value, err := redis.Int64(conn.Do("GET", key)) | |
| 137 | + if err != nil { | |
| 138 | + //fmt.Println("redis get failed:", err) | |
| 139 | + } | |
| 140 | + return value, err | |
| 141 | +} | |
| 142 | + | |
| 143 | +func (client *RedisClient) GetUint64(key string) (uint64, error) { | |
| 144 | + conn := client.m_pool.Get() | |
| 145 | + defer conn.Close() | |
| 146 | + value, err := redis.Uint64(conn.Do("GET", key)) | |
| 147 | + if err != nil { | |
| 148 | + //fmt.Println("redis get failed:", err) | |
| 149 | + } | |
| 150 | + return value, err | |
| 151 | +} | |
| 152 | + | |
| 153 | +func (client *RedisClient) GetInt(key string) (int, error) { | |
| 154 | + conn := client.m_pool.Get() | |
| 155 | + defer conn.Close() | |
| 156 | + value, err := redis.Int(conn.Do("GET", key)) | |
| 157 | + if err != nil { | |
| 158 | + //fmt.Println("redis get failed:", err) | |
| 159 | + } | |
| 160 | + return value, err | |
| 161 | +} | |
| 162 | + | |
| 163 | +func (client *RedisClient) GetBool(key string) (bool, error) { | |
| 164 | + conn := client.m_pool.Get() | |
| 165 | + defer conn.Close() | |
| 166 | + value, err := redis.Bool(conn.Do("GET", key)) | |
| 167 | + if err != nil { | |
| 168 | + //fmt.Println("redis get failed:", err) | |
| 169 | + } | |
| 170 | + return value, err | |
| 171 | +} | |
| 172 | + | |
| 173 | +func (client *RedisClient) HExists(key string, field string) (bool, error) { | |
| 174 | + conn := client.m_pool.Get() | |
| 175 | + defer conn.Close() | |
| 176 | + value, err := redis.Bool(conn.Do("HEXISTS", key, field)) | |
| 177 | + if err != nil { | |
| 178 | + //fmt.Println("redis get failed:", err) | |
| 179 | + } | |
| 180 | + return value, err | |
| 181 | +} | |
| 182 | + | |
| 183 | +func (client *RedisClient) Exists(key string) (bool, error) { | |
| 184 | + conn := client.m_pool.Get() | |
| 185 | + defer conn.Close() | |
| 186 | + value, err := redis.Bool(conn.Do("EXISTS", key)) | |
| 187 | + if err != nil { | |
| 188 | + //fmt.Println("redis get failed:", err) | |
| 189 | + } | |
| 190 | + return value, err | |
| 191 | +} | |
| 192 | + | |
| 193 | +func (client *RedisClient) HMSet(key string, data interface{}) error { | |
| 194 | + conn := client.m_pool.Get() | |
| 195 | + defer conn.Close() | |
| 196 | + if _, err := conn.Do("HMSET", redis.Args{}.Add(key).AddFlat(data)...); err != nil { | |
| 197 | + //fmt.Println("redis set failed:", err) | |
| 198 | + return err | |
| 199 | + } | |
| 200 | + | |
| 201 | + return nil | |
| 202 | +} | |
| 203 | + | |
| 204 | +func (client *RedisClient) HSet(key, field, value string) error { | |
| 205 | + conn := client.m_pool.Get() | |
| 206 | + defer conn.Close() | |
| 207 | + if _, err := conn.Do("HSET", key, field, value); err != nil { | |
| 208 | + //fmt.Println("redis set failed:", err) | |
| 209 | + return err | |
| 210 | + } | |
| 211 | + | |
| 212 | + return nil | |
| 213 | +} | |
| 214 | + | |
| 215 | +func (client *RedisClient) Zadd(key string, score float64, member string) error { | |
| 216 | + conn := client.m_pool.Get() | |
| 217 | + defer conn.Close() | |
| 218 | + if _, err := conn.Do("ZADD", key, score, member); err != nil { | |
| 219 | + //fmt.Println("redis set failed:", err) | |
| 220 | + return err | |
| 221 | + } | |
| 222 | + | |
| 223 | + return nil | |
| 224 | +} | |
| 225 | + | |
| 226 | +func (client *RedisClient) ZRevRangewithIndex(key string, start, end int) ([]interface{}, error) { | |
| 227 | + conn := client.m_pool.Get() | |
| 228 | + defer conn.Close() | |
| 229 | + v, err := redis.Values(conn.Do("ZREVRANGE", key, start, end)) | |
| 230 | + return v, err | |
| 231 | +} | |
| 232 | + | |
| 233 | +func (client *RedisClient) HSetBytes(key, field string, value []byte) error { | |
| 234 | + conn := client.m_pool.Get() | |
| 235 | + defer conn.Close() | |
| 236 | + if _, err := conn.Do("HSET", key, field, value); err != nil { | |
| 237 | + //fmt.Println("redis set failed:", err) | |
| 238 | + return err | |
| 239 | + } | |
| 240 | + | |
| 241 | + return nil | |
| 242 | +} | |
| 243 | + | |
| 244 | +func (client *RedisClient) HDel(key, field string) error { | |
| 245 | + conn := client.m_pool.Get() | |
| 246 | + defer conn.Close() | |
| 247 | + if _, err := conn.Do("HDEL", key, field); err != nil { | |
| 248 | + return err | |
| 249 | + } | |
| 250 | + | |
| 251 | + return nil | |
| 252 | +} | |
| 253 | + | |
| 254 | +func (client *RedisClient) HGetAll(key string, data interface{}) error { | |
| 255 | + conn := client.m_pool.Get() | |
| 256 | + defer conn.Close() | |
| 257 | + v, err := redis.Values(conn.Do("HGETALL", key)) | |
| 258 | + if err != nil { | |
| 259 | + //fmt.Println("redis set failed:", err) | |
| 260 | + return err | |
| 261 | + } | |
| 262 | + if err := redis.ScanStruct(v, data); err != nil { | |
| 263 | + return err | |
| 264 | + } | |
| 265 | + return nil | |
| 266 | +} | |
| 267 | + | |
| 268 | +func (client *RedisClient) HGetAllValues(key string) ([]interface{}, error) { | |
| 269 | + conn := client.m_pool.Get() | |
| 270 | + defer conn.Close() | |
| 271 | + v, err := redis.Values(conn.Do("HGETALL", key)) | |
| 272 | + return v, err | |
| 273 | +} | |
| 274 | + | |
| 275 | +func (client *RedisClient) HGetAllKeys(key string) ([]interface{}, error) { | |
| 276 | + conn := client.m_pool.Get() | |
| 277 | + defer conn.Close() | |
| 278 | + v, err := redis.Values(conn.Do("hkeys", key)) | |
| 279 | + return v, err | |
| 280 | +} | |
| 281 | + | |
| 282 | +func (client *RedisClient) HGet(key string, field string) (string, error) { | |
| 283 | + conn := client.m_pool.Get() | |
| 284 | + defer conn.Close() | |
| 285 | + value, err := redis.String(conn.Do("HGET", key, field)) | |
| 286 | + if err != nil { | |
| 287 | + //fmt.Println("redis set failed:", err) | |
| 288 | + return "", err | |
| 289 | + } | |
| 290 | + return value, nil | |
| 291 | +} | |
| 292 | +func (client *RedisClient) HGetBytes(key, field string) ([]byte, error) { | |
| 293 | + conn := client.m_pool.Get() | |
| 294 | + defer conn.Close() | |
| 295 | + value, err := conn.Do("HGET", key, field) | |
| 296 | + if err != nil { | |
| 297 | + fmt.Println("redis hget failed:", err) | |
| 298 | + return []byte{}, err | |
| 299 | + } | |
| 300 | + if value == nil { | |
| 301 | + return []byte{}, errors.New("value nil") | |
| 302 | + } | |
| 303 | + return value.([]byte), err | |
| 304 | +} | |
| 305 | + | |
| 306 | +func (client *RedisClient) HGetInt(key string, field string) (int, error) { | |
| 307 | + conn := client.m_pool.Get() | |
| 308 | + defer conn.Close() | |
| 309 | + value, err := redis.Int(conn.Do("HGET", key, field)) | |
| 310 | + if err != nil { | |
| 311 | + //fmt.Println("redis set failed:", err) | |
| 312 | + return 0, err | |
| 313 | + } | |
| 314 | + return value, nil | |
| 315 | +} | |
| 316 | + | |
| 317 | +func (client *RedisClient) LPop(listName string) (string, error) { | |
| 318 | + conn := client.m_pool.Get() | |
| 319 | + defer conn.Close() | |
| 320 | + value, err := redis.String(conn.Do("LPOP", listName)) | |
| 321 | + if err != nil { | |
| 322 | + //fmt.Println("LPop redis get failed:", err) | |
| 323 | + } | |
| 324 | + return value, err | |
| 325 | +} | |
| 326 | + | |
| 327 | +func (client *RedisClient) LRem(listName string, count int, value string) error { | |
| 328 | + conn := client.m_pool.Get() | |
| 329 | + defer conn.Close() | |
| 330 | + cntStr := strconv.Itoa(count) | |
| 331 | + _, err := redis.Int(conn.Do("LREM", listName, cntStr, value)) | |
| 332 | + if err != nil { | |
| 333 | + //fmt.Println("LPop redis get failed:", err) | |
| 334 | + } | |
| 335 | + return err | |
| 336 | +} | |
| 337 | + | |
| 338 | +func (client *RedisClient) RPush(listName string, element string) error { | |
| 339 | + conn := client.m_pool.Get() | |
| 340 | + defer conn.Close() | |
| 341 | + _, err := redis.Int(conn.Do("RPUSH", listName, element)) | |
| 342 | + if err != nil { | |
| 343 | + //fmt.Println("RPush redis get failed:", err) | |
| 344 | + } | |
| 345 | + return err | |
| 346 | +} | |
| 347 | + | |
| 348 | +func (client *RedisClient) LLen(listName string) (int, error) { | |
| 349 | + conn := client.m_pool.Get() | |
| 350 | + defer conn.Close() | |
| 351 | + value, err := redis.Int(conn.Do("LLEN", listName)) | |
| 352 | + if err != nil { | |
| 353 | + //fmt.Println("LLEN redis get failed:", err) | |
| 354 | + } | |
| 355 | + return value, err | |
| 356 | +} | |
| 357 | + | |
| 358 | +func (client *RedisClient) LIndex(listName string, index int) (string, error) { | |
| 359 | + conn := client.m_pool.Get() | |
| 360 | + defer conn.Close() | |
| 361 | + idxStr := strconv.Itoa(index) | |
| 362 | + value, err := redis.String(conn.Do("LINDEX", listName, idxStr)) | |
| 363 | + if err != nil { | |
| 364 | + //fmt.Println("LIndex redis get failed:", err) | |
| 365 | + } | |
| 366 | + return value, err | |
| 367 | +} | |
| 368 | + | |
| 369 | +func (client *RedisClient) LRange(listName string, start int, stop int) ([]string, error) { | |
| 370 | + conn := client.m_pool.Get() | |
| 371 | + defer conn.Close() | |
| 372 | + value, err := redis.Strings(conn.Do("LRANGE", listName, start, stop)) | |
| 373 | + if err != nil { | |
| 374 | + //fmt.Println("redis get failed:", err) | |
| 375 | + } | |
| 376 | + return value, err | |
| 377 | +} | |
| 378 | + | |
| 379 | +func (client *RedisClient) Incr(key string) (uint64, error) { | |
| 380 | + conn := client.m_pool.Get() | |
| 381 | + defer conn.Close() | |
| 382 | + value, err := redis.Uint64(conn.Do("INCR", key)) | |
| 383 | + if err != nil { | |
| 384 | + //fmt.Println("RPush redis get failed:", err) | |
| 385 | + } | |
| 386 | + return value, err | |
| 387 | +} | |
| 388 | + | |
| 389 | +func (client *RedisClient) IncrBy(key string, incr int) (uint64, error) { | |
| 390 | + conn := client.m_pool.Get() | |
| 391 | + defer conn.Close() | |
| 392 | + value, err := redis.Uint64(conn.Do("INCRBY", key, incr)) | |
| 393 | + if err != nil { | |
| 394 | + fmt.Println("redis get failed:", err) | |
| 395 | + } | |
| 396 | + return value, err | |
| 397 | +} | |
| 398 | + | |
| 399 | +func (client *RedisClient) HMGets(key string) ([]interface{}, error) { | |
| 400 | + conn := client.m_pool.Get() | |
| 401 | + defer conn.Close() | |
| 402 | + v, err := redis.Values(conn.Do("HGETALL", key)) | |
| 403 | + if err != nil { | |
| 404 | + fmt.Println("redis get failed:", err) | |
| 405 | + return nil, err | |
| 406 | + } | |
| 407 | + return v, err | |
| 408 | +} | ... | ... |
No preview for this file type
src/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md
0 → 100644
| ... | ... | @@ -0,0 +1 @@ |
| 1 | +Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis | ... | ... |
| ... | ... | @@ -0,0 +1,19 @@ |
| 1 | +language: go | |
| 2 | +sudo: false | |
| 3 | +services: | |
| 4 | + - redis-server | |
| 5 | + | |
| 6 | +go: | |
| 7 | + - 1.4 | |
| 8 | + - 1.5 | |
| 9 | + - 1.6 | |
| 10 | + - 1.7 | |
| 11 | + - 1.8 | |
| 12 | + - 1.9 | |
| 13 | + - tip | |
| 14 | + | |
| 15 | +script: | |
| 16 | + - go get -t -v ./... | |
| 17 | + - diff -u <(echo -n) <(gofmt -d .) | |
| 18 | + - go vet $(go list ./... | grep -v /vendor/) | |
| 19 | + - go test -v -race ./... | ... | ... |
| ... | ... | @@ -0,0 +1,175 @@ |
| 1 | + | |
| 2 | + Apache License | |
| 3 | + Version 2.0, January 2004 | |
| 4 | + http://www.apache.org/licenses/ | |
| 5 | + | |
| 6 | + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | |
| 7 | + | |
| 8 | + 1. Definitions. | |
| 9 | + | |
| 10 | + "License" shall mean the terms and conditions for use, reproduction, | |
| 11 | + and distribution as defined by Sections 1 through 9 of this document. | |
| 12 | + | |
| 13 | + "Licensor" shall mean the copyright owner or entity authorized by | |
| 14 | + the copyright owner that is granting the License. | |
| 15 | + | |
| 16 | + "Legal Entity" shall mean the union of the acting entity and all | |
| 17 | + other entities that control, are controlled by, or are under common | |
| 18 | + control with that entity. For the purposes of this definition, | |
| 19 | + "control" means (i) the power, direct or indirect, to cause the | |
| 20 | + direction or management of such entity, whether by contract or | |
| 21 | + otherwise, or (ii) ownership of fifty percent (50%) or more of the | |
| 22 | + outstanding shares, or (iii) beneficial ownership of such entity. | |
| 23 | + | |
| 24 | + "You" (or "Your") shall mean an individual or Legal Entity | |
| 25 | + exercising permissions granted by this License. | |
| 26 | + | |
| 27 | + "Source" form shall mean the preferred form for making modifications, | |
| 28 | + including but not limited to software source code, documentation | |
| 29 | + source, and configuration files. | |
| 30 | + | |
| 31 | + "Object" form shall mean any form resulting from mechanical | |
| 32 | + transformation or translation of a Source form, including but | |
| 33 | + not limited to compiled object code, generated documentation, | |
| 34 | + and conversions to other media types. | |
| 35 | + | |
| 36 | + "Work" shall mean the work of authorship, whether in Source or | |
| 37 | + Object form, made available under the License, as indicated by a | |
| 38 | + copyright notice that is included in or attached to the work | |
| 39 | + (an example is provided in the Appendix below). | |
| 40 | + | |
| 41 | + "Derivative Works" shall mean any work, whether in Source or Object | |
| 42 | + form, that is based on (or derived from) the Work and for which the | |
| 43 | + editorial revisions, annotations, elaborations, or other modifications | |
| 44 | + represent, as a whole, an original work of authorship. For the purposes | |
| 45 | + of this License, Derivative Works shall not include works that remain | |
| 46 | + separable from, or merely link (or bind by name) to the interfaces of, | |
| 47 | + the Work and Derivative Works thereof. | |
| 48 | + | |
| 49 | + "Contribution" shall mean any work of authorship, including | |
| 50 | + the original version of the Work and any modifications or additions | |
| 51 | + to that Work or Derivative Works thereof, that is intentionally | |
| 52 | + submitted to Licensor for inclusion in the Work by the copyright owner | |
| 53 | + or by an individual or Legal Entity authorized to submit on behalf of | |
| 54 | + the copyright owner. For the purposes of this definition, "submitted" | |
| 55 | + means any form of electronic, verbal, or written communication sent | |
| 56 | + to the Licensor or its representatives, including but not limited to | |
| 57 | + communication on electronic mailing lists, source code control systems, | |
| 58 | + and issue tracking systems that are managed by, or on behalf of, the | |
| 59 | + Licensor for the purpose of discussing and improving the Work, but | |
| 60 | + excluding communication that is conspicuously marked or otherwise | |
| 61 | + designated in writing by the copyright owner as "Not a Contribution." | |
| 62 | + | |
| 63 | + "Contributor" shall mean Licensor and any individual or Legal Entity | |
| 64 | + on behalf of whom a Contribution has been received by Licensor and | |
| 65 | + subsequently incorporated within the Work. | |
| 66 | + | |
| 67 | + 2. Grant of Copyright License. Subject to the terms and conditions of | |
| 68 | + this License, each Contributor hereby grants to You a perpetual, | |
| 69 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 70 | + copyright license to reproduce, prepare Derivative Works of, | |
| 71 | + publicly display, publicly perform, sublicense, and distribute the | |
| 72 | + Work and such Derivative Works in Source or Object form. | |
| 73 | + | |
| 74 | + 3. Grant of Patent License. Subject to the terms and conditions of | |
| 75 | + this License, each Contributor hereby grants to You a perpetual, | |
| 76 | + worldwide, non-exclusive, no-charge, royalty-free, irrevocable | |
| 77 | + (except as stated in this section) patent license to make, have made, | |
| 78 | + use, offer to sell, sell, import, and otherwise transfer the Work, | |
| 79 | + where such license applies only to those patent claims licensable | |
| 80 | + by such Contributor that are necessarily infringed by their | |
| 81 | + Contribution(s) alone or by combination of their Contribution(s) | |
| 82 | + with the Work to which such Contribution(s) was submitted. If You | |
| 83 | + institute patent litigation against any entity (including a | |
| 84 | + cross-claim or counterclaim in a lawsuit) alleging that the Work | |
| 85 | + or a Contribution incorporated within the Work constitutes direct | |
| 86 | + or contributory patent infringement, then any patent licenses | |
| 87 | + granted to You under this License for that Work shall terminate | |
| 88 | + as of the date such litigation is filed. | |
| 89 | + | |
| 90 | + 4. Redistribution. You may reproduce and distribute copies of the | |
| 91 | + Work or Derivative Works thereof in any medium, with or without | |
| 92 | + modifications, and in Source or Object form, provided that You | |
| 93 | + meet the following conditions: | |
| 94 | + | |
| 95 | + (a) You must give any other recipients of the Work or | |
| 96 | + Derivative Works a copy of this License; and | |
| 97 | + | |
| 98 | + (b) You must cause any modified files to carry prominent notices | |
| 99 | + stating that You changed the files; and | |
| 100 | + | |
| 101 | + (c) You must retain, in the Source form of any Derivative Works | |
| 102 | + that You distribute, all copyright, patent, trademark, and | |
| 103 | + attribution notices from the Source form of the Work, | |
| 104 | + excluding those notices that do not pertain to any part of | |
| 105 | + the Derivative Works; and | |
| 106 | + | |
| 107 | + (d) If the Work includes a "NOTICE" text file as part of its | |
| 108 | + distribution, then any Derivative Works that You distribute must | |
| 109 | + include a readable copy of the attribution notices contained | |
| 110 | + within such NOTICE file, excluding those notices that do not | |
| 111 | + pertain to any part of the Derivative Works, in at least one | |
| 112 | + of the following places: within a NOTICE text file distributed | |
| 113 | + as part of the Derivative Works; within the Source form or | |
| 114 | + documentation, if provided along with the Derivative Works; or, | |
| 115 | + within a display generated by the Derivative Works, if and | |
| 116 | + wherever such third-party notices normally appear. The contents | |
| 117 | + of the NOTICE file are for informational purposes only and | |
| 118 | + do not modify the License. You may add Your own attribution | |
| 119 | + notices within Derivative Works that You distribute, alongside | |
| 120 | + or as an addendum to the NOTICE text from the Work, provided | |
| 121 | + that such additional attribution notices cannot be construed | |
| 122 | + as modifying the License. | |
| 123 | + | |
| 124 | + You may add Your own copyright statement to Your modifications and | |
| 125 | + may provide additional or different license terms and conditions | |
| 126 | + for use, reproduction, or distribution of Your modifications, or | |
| 127 | + for any such Derivative Works as a whole, provided Your use, | |
| 128 | + reproduction, and distribution of the Work otherwise complies with | |
| 129 | + the conditions stated in this License. | |
| 130 | + | |
| 131 | + 5. Submission of Contributions. Unless You explicitly state otherwise, | |
| 132 | + any Contribution intentionally submitted for inclusion in the Work | |
| 133 | + by You to the Licensor shall be under the terms and conditions of | |
| 134 | + this License, without any additional terms or conditions. | |
| 135 | + Notwithstanding the above, nothing herein shall supersede or modify | |
| 136 | + the terms of any separate license agreement you may have executed | |
| 137 | + with Licensor regarding such Contributions. | |
| 138 | + | |
| 139 | + 6. Trademarks. This License does not grant permission to use the trade | |
| 140 | + names, trademarks, service marks, or product names of the Licensor, | |
| 141 | + except as required for reasonable and customary use in describing the | |
| 142 | + origin of the Work and reproducing the content of the NOTICE file. | |
| 143 | + | |
| 144 | + 7. Disclaimer of Warranty. Unless required by applicable law or | |
| 145 | + agreed to in writing, Licensor provides the Work (and each | |
| 146 | + Contributor provides its Contributions) on an "AS IS" BASIS, | |
| 147 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | |
| 148 | + implied, including, without limitation, any warranties or conditions | |
| 149 | + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | |
| 150 | + PARTICULAR PURPOSE. You are solely responsible for determining the | |
| 151 | + appropriateness of using or redistributing the Work and assume any | |
| 152 | + risks associated with Your exercise of permissions under this License. | |
| 153 | + | |
| 154 | + 8. Limitation of Liability. In no event and under no legal theory, | |
| 155 | + whether in tort (including negligence), contract, or otherwise, | |
| 156 | + unless required by applicable law (such as deliberate and grossly | |
| 157 | + negligent acts) or agreed to in writing, shall any Contributor be | |
| 158 | + liable to You for damages, including any direct, indirect, special, | |
| 159 | + incidental, or consequential damages of any character arising as a | |
| 160 | + result of this License or out of the use or inability to use the | |
| 161 | + Work (including but not limited to damages for loss of goodwill, | |
| 162 | + work stoppage, computer failure or malfunction, or any and all | |
| 163 | + other commercial damages or losses), even if such Contributor | |
| 164 | + has been advised of the possibility of such damages. | |
| 165 | + | |
| 166 | + 9. Accepting Warranty or Additional Liability. While redistributing | |
| 167 | + the Work or Derivative Works thereof, You may choose to offer, | |
| 168 | + and charge a fee for, acceptance of support, warranty, indemnity, | |
| 169 | + or other liability obligations and/or rights consistent with this | |
| 170 | + License. However, in accepting such obligations, You may act only | |
| 171 | + on Your own behalf and on Your sole responsibility, not on behalf | |
| 172 | + of any other Contributor, and only if You agree to indemnify, | |
| 173 | + defend, and hold each Contributor harmless for any liability | |
| 174 | + incurred by, or claims asserted against, such Contributor by reason | |
| 175 | + of your accepting any such warranty or additional liability. | ... | ... |
| ... | ... | @@ -0,0 +1,51 @@ |
| 1 | +Redigo | |
| 2 | +====== | |
| 3 | + | |
| 4 | +[](https://travis-ci.org/garyburd/redigo) | |
| 5 | +[](https://godoc.org/github.com/garyburd/redigo/redis) | |
| 6 | + | |
| 7 | +Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database. | |
| 8 | + | |
| 9 | +Features | |
| 10 | +------- | |
| 11 | + | |
| 12 | +* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands. | |
| 13 | +* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions. | |
| 14 | +* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe). | |
| 15 | +* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool). | |
| 16 | +* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA. | |
| 17 | +* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies. | |
| 18 | + | |
| 19 | +Documentation | |
| 20 | +------------- | |
| 21 | + | |
| 22 | +- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis) | |
| 23 | +- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ) | |
| 24 | +- [Examples](https://godoc.org/github.com/garyburd/redigo/redis#pkg-examples) | |
| 25 | + | |
| 26 | +Installation | |
| 27 | +------------ | |
| 28 | + | |
| 29 | +Install Redigo using the "go get" command: | |
| 30 | + | |
| 31 | + go get github.com/garyburd/redigo/redis | |
| 32 | + | |
| 33 | +The Go distribution is Redigo's only dependency. | |
| 34 | + | |
| 35 | +Related Projects | |
| 36 | +---------------- | |
| 37 | + | |
| 38 | +- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo. | |
| 39 | +- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation. | |
| 40 | +- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo | |
| 41 | +- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo | |
| 42 | + | |
| 43 | +Contributing | |
| 44 | +------------ | |
| 45 | + | |
| 46 | +See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md). | |
| 47 | + | |
| 48 | +License | |
| 49 | +------- | |
| 50 | + | |
| 51 | +Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html). | ... | ... |
| ... | ... | @@ -0,0 +1,54 @@ |
| 1 | +// Copyright 2014 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package internal // import "github.com/garyburd/redigo/internal" | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "strings" | |
| 19 | +) | |
| 20 | + | |
| 21 | +const ( | |
| 22 | + WatchState = 1 << iota | |
| 23 | + MultiState | |
| 24 | + SubscribeState | |
| 25 | + MonitorState | |
| 26 | +) | |
| 27 | + | |
| 28 | +type CommandInfo struct { | |
| 29 | + Set, Clear int | |
| 30 | +} | |
| 31 | + | |
| 32 | +var commandInfos = map[string]CommandInfo{ | |
| 33 | + "WATCH": {Set: WatchState}, | |
| 34 | + "UNWATCH": {Clear: WatchState}, | |
| 35 | + "MULTI": {Set: MultiState}, | |
| 36 | + "EXEC": {Clear: WatchState | MultiState}, | |
| 37 | + "DISCARD": {Clear: WatchState | MultiState}, | |
| 38 | + "PSUBSCRIBE": {Set: SubscribeState}, | |
| 39 | + "SUBSCRIBE": {Set: SubscribeState}, | |
| 40 | + "MONITOR": {Set: MonitorState}, | |
| 41 | +} | |
| 42 | + | |
| 43 | +func init() { | |
| 44 | + for n, ci := range commandInfos { | |
| 45 | + commandInfos[strings.ToLower(n)] = ci | |
| 46 | + } | |
| 47 | +} | |
| 48 | + | |
| 49 | +func LookupCommandInfo(commandName string) CommandInfo { | |
| 50 | + if ci, ok := commandInfos[commandName]; ok { | |
| 51 | + return ci | |
| 52 | + } | |
| 53 | + return commandInfos[strings.ToUpper(commandName)] | |
| 54 | +} | ... | ... |
src/github.com/garyburd/redigo/internal/commandinfo_test.go
0 → 100644
| ... | ... | @@ -0,0 +1,27 @@ |
| 1 | +package internal | |
| 2 | + | |
| 3 | +import "testing" | |
| 4 | + | |
| 5 | +func TestLookupCommandInfo(t *testing.T) { | |
| 6 | + for _, n := range []string{"watch", "WATCH", "wAtch"} { | |
| 7 | + if LookupCommandInfo(n) == (CommandInfo{}) { | |
| 8 | + t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n) | |
| 9 | + } | |
| 10 | + } | |
| 11 | +} | |
| 12 | + | |
| 13 | +func benchmarkLookupCommandInfo(b *testing.B, names ...string) { | |
| 14 | + for i := 0; i < b.N; i++ { | |
| 15 | + for _, c := range names { | |
| 16 | + LookupCommandInfo(c) | |
| 17 | + } | |
| 18 | + } | |
| 19 | +} | |
| 20 | + | |
| 21 | +func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) { | |
| 22 | + benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR") | |
| 23 | +} | |
| 24 | + | |
| 25 | +func BenchmarkLookupCommandInfoMixedCase(b *testing.B) { | |
| 26 | + benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR") | |
| 27 | +} | ... | ... |
src/github.com/garyburd/redigo/internal/redistest/testdb.go
0 → 100644
| ... | ... | @@ -0,0 +1,68 @@ |
| 1 | +// Copyright 2014 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +// Package redistest contains utilities for writing Redigo tests. | |
| 16 | +package redistest | |
| 17 | + | |
| 18 | +import ( | |
| 19 | + "errors" | |
| 20 | + "time" | |
| 21 | + | |
| 22 | + "github.com/garyburd/redigo/redis" | |
| 23 | +) | |
| 24 | + | |
| 25 | +type testConn struct { | |
| 26 | + redis.Conn | |
| 27 | +} | |
| 28 | + | |
| 29 | +func (t testConn) Close() error { | |
| 30 | + _, err := t.Conn.Do("SELECT", "9") | |
| 31 | + if err != nil { | |
| 32 | + return nil | |
| 33 | + } | |
| 34 | + _, err = t.Conn.Do("FLUSHDB") | |
| 35 | + if err != nil { | |
| 36 | + return err | |
| 37 | + } | |
| 38 | + return t.Conn.Close() | |
| 39 | +} | |
| 40 | + | |
| 41 | +// Dial dials the local Redis server and selects database 9. To prevent | |
| 42 | +// stomping on real data, DialTestDB fails if database 9 contains data. The | |
| 43 | +// returned connection flushes database 9 on close. | |
| 44 | +func Dial() (redis.Conn, error) { | |
| 45 | + c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second) | |
| 46 | + if err != nil { | |
| 47 | + return nil, err | |
| 48 | + } | |
| 49 | + | |
| 50 | + _, err = c.Do("SELECT", "9") | |
| 51 | + if err != nil { | |
| 52 | + c.Close() | |
| 53 | + return nil, err | |
| 54 | + } | |
| 55 | + | |
| 56 | + n, err := redis.Int(c.Do("DBSIZE")) | |
| 57 | + if err != nil { | |
| 58 | + c.Close() | |
| 59 | + return nil, err | |
| 60 | + } | |
| 61 | + | |
| 62 | + if n != 0 { | |
| 63 | + c.Close() | |
| 64 | + return nil, errors.New("database #9 is not empty, test can not continue") | |
| 65 | + } | |
| 66 | + | |
| 67 | + return testConn{c}, nil | |
| 68 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,651 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "bufio" | |
| 19 | + "bytes" | |
| 20 | + "crypto/tls" | |
| 21 | + "errors" | |
| 22 | + "fmt" | |
| 23 | + "io" | |
| 24 | + "net" | |
| 25 | + "net/url" | |
| 26 | + "regexp" | |
| 27 | + "strconv" | |
| 28 | + "sync" | |
| 29 | + "time" | |
| 30 | +) | |
| 31 | + | |
| 32 | +// conn is the low-level implementation of Conn | |
| 33 | +type conn struct { | |
| 34 | + // Shared | |
| 35 | + mu sync.Mutex | |
| 36 | + pending int | |
| 37 | + err error | |
| 38 | + conn net.Conn | |
| 39 | + | |
| 40 | + // Read | |
| 41 | + readTimeout time.Duration | |
| 42 | + br *bufio.Reader | |
| 43 | + | |
| 44 | + // Write | |
| 45 | + writeTimeout time.Duration | |
| 46 | + bw *bufio.Writer | |
| 47 | + | |
| 48 | + // Scratch space for formatting argument length. | |
| 49 | + // '*' or '$', length, "\r\n" | |
| 50 | + lenScratch [32]byte | |
| 51 | + | |
| 52 | + // Scratch space for formatting integers and floats. | |
| 53 | + numScratch [40]byte | |
| 54 | +} | |
| 55 | + | |
| 56 | +// DialTimeout acts like Dial but takes timeouts for establishing the | |
| 57 | +// connection to the server, writing a command and reading a reply. | |
| 58 | +// | |
| 59 | +// Deprecated: Use Dial with options instead. | |
| 60 | +func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) { | |
| 61 | + return Dial(network, address, | |
| 62 | + DialConnectTimeout(connectTimeout), | |
| 63 | + DialReadTimeout(readTimeout), | |
| 64 | + DialWriteTimeout(writeTimeout)) | |
| 65 | +} | |
| 66 | + | |
| 67 | +// DialOption specifies an option for dialing a Redis server. | |
| 68 | +type DialOption struct { | |
| 69 | + f func(*dialOptions) | |
| 70 | +} | |
| 71 | + | |
| 72 | +type dialOptions struct { | |
| 73 | + readTimeout time.Duration | |
| 74 | + writeTimeout time.Duration | |
| 75 | + dialer *net.Dialer | |
| 76 | + dial func(network, addr string) (net.Conn, error) | |
| 77 | + db int | |
| 78 | + password string | |
| 79 | + useTLS bool | |
| 80 | + skipVerify bool | |
| 81 | + tlsConfig *tls.Config | |
| 82 | +} | |
| 83 | + | |
| 84 | +// DialReadTimeout specifies the timeout for reading a single command reply. | |
| 85 | +func DialReadTimeout(d time.Duration) DialOption { | |
| 86 | + return DialOption{func(do *dialOptions) { | |
| 87 | + do.readTimeout = d | |
| 88 | + }} | |
| 89 | +} | |
| 90 | + | |
| 91 | +// DialWriteTimeout specifies the timeout for writing a single command. | |
| 92 | +func DialWriteTimeout(d time.Duration) DialOption { | |
| 93 | + return DialOption{func(do *dialOptions) { | |
| 94 | + do.writeTimeout = d | |
| 95 | + }} | |
| 96 | +} | |
| 97 | + | |
| 98 | +// DialConnectTimeout specifies the timeout for connecting to the Redis server when | |
| 99 | +// no DialNetDial option is specified. | |
| 100 | +func DialConnectTimeout(d time.Duration) DialOption { | |
| 101 | + return DialOption{func(do *dialOptions) { | |
| 102 | + do.dialer.Timeout = d | |
| 103 | + }} | |
| 104 | +} | |
| 105 | + | |
| 106 | +// DialKeepAlive specifies the keep-alive period for TCP connections to the Redis server | |
| 107 | +// when no DialNetDial option is specified. | |
| 108 | +// If zero, keep-alives are not enabled. If no DialKeepAlive option is specified then | |
| 109 | +// the default of 5 minutes is used to ensure that half-closed TCP sessions are detected. | |
| 110 | +func DialKeepAlive(d time.Duration) DialOption { | |
| 111 | + return DialOption{func(do *dialOptions) { | |
| 112 | + do.dialer.KeepAlive = d | |
| 113 | + }} | |
| 114 | +} | |
| 115 | + | |
| 116 | +// DialNetDial specifies a custom dial function for creating TCP | |
| 117 | +// connections, otherwise a net.Dialer customized via the other options is used. | |
| 118 | +// DialNetDial overrides DialConnectTimeout and DialKeepAlive. | |
| 119 | +func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption { | |
| 120 | + return DialOption{func(do *dialOptions) { | |
| 121 | + do.dial = dial | |
| 122 | + }} | |
| 123 | +} | |
| 124 | + | |
| 125 | +// DialDatabase specifies the database to select when dialing a connection. | |
| 126 | +func DialDatabase(db int) DialOption { | |
| 127 | + return DialOption{func(do *dialOptions) { | |
| 128 | + do.db = db | |
| 129 | + }} | |
| 130 | +} | |
| 131 | + | |
| 132 | +// DialPassword specifies the password to use when connecting to | |
| 133 | +// the Redis server. | |
| 134 | +func DialPassword(password string) DialOption { | |
| 135 | + return DialOption{func(do *dialOptions) { | |
| 136 | + do.password = password | |
| 137 | + }} | |
| 138 | +} | |
| 139 | + | |
| 140 | +// DialTLSConfig specifies the config to use when a TLS connection is dialed. | |
| 141 | +// Has no effect when not dialing a TLS connection. | |
| 142 | +func DialTLSConfig(c *tls.Config) DialOption { | |
| 143 | + return DialOption{func(do *dialOptions) { | |
| 144 | + do.tlsConfig = c | |
| 145 | + }} | |
| 146 | +} | |
| 147 | + | |
| 148 | +// DialTLSSkipVerify disables server name verification when connecting over | |
| 149 | +// TLS. Has no effect when not dialing a TLS connection. | |
| 150 | +func DialTLSSkipVerify(skip bool) DialOption { | |
| 151 | + return DialOption{func(do *dialOptions) { | |
| 152 | + do.skipVerify = skip | |
| 153 | + }} | |
| 154 | +} | |
| 155 | + | |
| 156 | +// DialUseTLS specifies whether TLS should be used when connecting to the | |
| 157 | +// server. This option is ignore by DialURL. | |
| 158 | +func DialUseTLS(useTLS bool) DialOption { | |
| 159 | + return DialOption{func(do *dialOptions) { | |
| 160 | + do.useTLS = useTLS | |
| 161 | + }} | |
| 162 | +} | |
| 163 | + | |
| 164 | +// Dial connects to the Redis server at the given network and | |
| 165 | +// address using the specified options. | |
| 166 | +func Dial(network, address string, options ...DialOption) (Conn, error) { | |
| 167 | + do := dialOptions{ | |
| 168 | + dialer: &net.Dialer{ | |
| 169 | + KeepAlive: time.Minute * 5, | |
| 170 | + }, | |
| 171 | + } | |
| 172 | + for _, option := range options { | |
| 173 | + option.f(&do) | |
| 174 | + } | |
| 175 | + if do.dial == nil { | |
| 176 | + do.dial = do.dialer.Dial | |
| 177 | + } | |
| 178 | + | |
| 179 | + netConn, err := do.dial(network, address) | |
| 180 | + if err != nil { | |
| 181 | + return nil, err | |
| 182 | + } | |
| 183 | + | |
| 184 | + if do.useTLS { | |
| 185 | + tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify) | |
| 186 | + if tlsConfig.ServerName == "" { | |
| 187 | + host, _, err := net.SplitHostPort(address) | |
| 188 | + if err != nil { | |
| 189 | + netConn.Close() | |
| 190 | + return nil, err | |
| 191 | + } | |
| 192 | + tlsConfig.ServerName = host | |
| 193 | + } | |
| 194 | + | |
| 195 | + tlsConn := tls.Client(netConn, tlsConfig) | |
| 196 | + if err := tlsConn.Handshake(); err != nil { | |
| 197 | + netConn.Close() | |
| 198 | + return nil, err | |
| 199 | + } | |
| 200 | + netConn = tlsConn | |
| 201 | + } | |
| 202 | + | |
| 203 | + c := &conn{ | |
| 204 | + conn: netConn, | |
| 205 | + bw: bufio.NewWriter(netConn), | |
| 206 | + br: bufio.NewReader(netConn), | |
| 207 | + readTimeout: do.readTimeout, | |
| 208 | + writeTimeout: do.writeTimeout, | |
| 209 | + } | |
| 210 | + | |
| 211 | + if do.password != "" { | |
| 212 | + if _, err := c.Do("AUTH", do.password); err != nil { | |
| 213 | + netConn.Close() | |
| 214 | + return nil, err | |
| 215 | + } | |
| 216 | + } | |
| 217 | + | |
| 218 | + if do.db != 0 { | |
| 219 | + if _, err := c.Do("SELECT", do.db); err != nil { | |
| 220 | + netConn.Close() | |
| 221 | + return nil, err | |
| 222 | + } | |
| 223 | + } | |
| 224 | + | |
| 225 | + return c, nil | |
| 226 | +} | |
| 227 | + | |
| 228 | +var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`) | |
| 229 | + | |
| 230 | +// DialURL connects to a Redis server at the given URL using the Redis | |
| 231 | +// URI scheme. URLs should follow the draft IANA specification for the | |
| 232 | +// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis). | |
| 233 | +func DialURL(rawurl string, options ...DialOption) (Conn, error) { | |
| 234 | + u, err := url.Parse(rawurl) | |
| 235 | + if err != nil { | |
| 236 | + return nil, err | |
| 237 | + } | |
| 238 | + | |
| 239 | + if u.Scheme != "redis" && u.Scheme != "rediss" { | |
| 240 | + return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme) | |
| 241 | + } | |
| 242 | + | |
| 243 | + // As per the IANA draft spec, the host defaults to localhost and | |
| 244 | + // the port defaults to 6379. | |
| 245 | + host, port, err := net.SplitHostPort(u.Host) | |
| 246 | + if err != nil { | |
| 247 | + // assume port is missing | |
| 248 | + host = u.Host | |
| 249 | + port = "6379" | |
| 250 | + } | |
| 251 | + if host == "" { | |
| 252 | + host = "localhost" | |
| 253 | + } | |
| 254 | + address := net.JoinHostPort(host, port) | |
| 255 | + | |
| 256 | + if u.User != nil { | |
| 257 | + password, isSet := u.User.Password() | |
| 258 | + if isSet { | |
| 259 | + options = append(options, DialPassword(password)) | |
| 260 | + } | |
| 261 | + } | |
| 262 | + | |
| 263 | + match := pathDBRegexp.FindStringSubmatch(u.Path) | |
| 264 | + if len(match) == 2 { | |
| 265 | + db := 0 | |
| 266 | + if len(match[1]) > 0 { | |
| 267 | + db, err = strconv.Atoi(match[1]) | |
| 268 | + if err != nil { | |
| 269 | + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) | |
| 270 | + } | |
| 271 | + } | |
| 272 | + if db != 0 { | |
| 273 | + options = append(options, DialDatabase(db)) | |
| 274 | + } | |
| 275 | + } else if u.Path != "" { | |
| 276 | + return nil, fmt.Errorf("invalid database: %s", u.Path[1:]) | |
| 277 | + } | |
| 278 | + | |
| 279 | + options = append(options, DialUseTLS(u.Scheme == "rediss")) | |
| 280 | + | |
| 281 | + return Dial("tcp", address, options...) | |
| 282 | +} | |
| 283 | + | |
| 284 | +// NewConn returns a new Redigo connection for the given net connection. | |
| 285 | +func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn { | |
| 286 | + return &conn{ | |
| 287 | + conn: netConn, | |
| 288 | + bw: bufio.NewWriter(netConn), | |
| 289 | + br: bufio.NewReader(netConn), | |
| 290 | + readTimeout: readTimeout, | |
| 291 | + writeTimeout: writeTimeout, | |
| 292 | + } | |
| 293 | +} | |
| 294 | + | |
| 295 | +func (c *conn) Close() error { | |
| 296 | + c.mu.Lock() | |
| 297 | + err := c.err | |
| 298 | + if c.err == nil { | |
| 299 | + c.err = errors.New("redigo: closed") | |
| 300 | + err = c.conn.Close() | |
| 301 | + } | |
| 302 | + c.mu.Unlock() | |
| 303 | + return err | |
| 304 | +} | |
| 305 | + | |
| 306 | +func (c *conn) fatal(err error) error { | |
| 307 | + c.mu.Lock() | |
| 308 | + if c.err == nil { | |
| 309 | + c.err = err | |
| 310 | + // Close connection to force errors on subsequent calls and to unblock | |
| 311 | + // other reader or writer. | |
| 312 | + c.conn.Close() | |
| 313 | + } | |
| 314 | + c.mu.Unlock() | |
| 315 | + return err | |
| 316 | +} | |
| 317 | + | |
| 318 | +func (c *conn) Err() error { | |
| 319 | + c.mu.Lock() | |
| 320 | + err := c.err | |
| 321 | + c.mu.Unlock() | |
| 322 | + return err | |
| 323 | +} | |
| 324 | + | |
| 325 | +func (c *conn) writeLen(prefix byte, n int) error { | |
| 326 | + c.lenScratch[len(c.lenScratch)-1] = '\n' | |
| 327 | + c.lenScratch[len(c.lenScratch)-2] = '\r' | |
| 328 | + i := len(c.lenScratch) - 3 | |
| 329 | + for { | |
| 330 | + c.lenScratch[i] = byte('0' + n%10) | |
| 331 | + i -= 1 | |
| 332 | + n = n / 10 | |
| 333 | + if n == 0 { | |
| 334 | + break | |
| 335 | + } | |
| 336 | + } | |
| 337 | + c.lenScratch[i] = prefix | |
| 338 | + _, err := c.bw.Write(c.lenScratch[i:]) | |
| 339 | + return err | |
| 340 | +} | |
| 341 | + | |
| 342 | +func (c *conn) writeString(s string) error { | |
| 343 | + c.writeLen('$', len(s)) | |
| 344 | + c.bw.WriteString(s) | |
| 345 | + _, err := c.bw.WriteString("\r\n") | |
| 346 | + return err | |
| 347 | +} | |
| 348 | + | |
| 349 | +func (c *conn) writeBytes(p []byte) error { | |
| 350 | + c.writeLen('$', len(p)) | |
| 351 | + c.bw.Write(p) | |
| 352 | + _, err := c.bw.WriteString("\r\n") | |
| 353 | + return err | |
| 354 | +} | |
| 355 | + | |
| 356 | +func (c *conn) writeInt64(n int64) error { | |
| 357 | + return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10)) | |
| 358 | +} | |
| 359 | + | |
| 360 | +func (c *conn) writeFloat64(n float64) error { | |
| 361 | + return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64)) | |
| 362 | +} | |
| 363 | + | |
| 364 | +func (c *conn) writeCommand(cmd string, args []interface{}) error { | |
| 365 | + c.writeLen('*', 1+len(args)) | |
| 366 | + if err := c.writeString(cmd); err != nil { | |
| 367 | + return err | |
| 368 | + } | |
| 369 | + for _, arg := range args { | |
| 370 | + if err := c.writeArg(arg, true); err != nil { | |
| 371 | + return err | |
| 372 | + } | |
| 373 | + } | |
| 374 | + return nil | |
| 375 | +} | |
| 376 | + | |
| 377 | +func (c *conn) writeArg(arg interface{}, argumentTypeOK bool) (err error) { | |
| 378 | + switch arg := arg.(type) { | |
| 379 | + case string: | |
| 380 | + return c.writeString(arg) | |
| 381 | + case []byte: | |
| 382 | + return c.writeBytes(arg) | |
| 383 | + case int: | |
| 384 | + return c.writeInt64(int64(arg)) | |
| 385 | + case int64: | |
| 386 | + return c.writeInt64(arg) | |
| 387 | + case float64: | |
| 388 | + return c.writeFloat64(arg) | |
| 389 | + case bool: | |
| 390 | + if arg { | |
| 391 | + return c.writeString("1") | |
| 392 | + } else { | |
| 393 | + return c.writeString("0") | |
| 394 | + } | |
| 395 | + case nil: | |
| 396 | + return c.writeString("") | |
| 397 | + case Argument: | |
| 398 | + if argumentTypeOK { | |
| 399 | + return c.writeArg(arg.RedisArg(), false) | |
| 400 | + } | |
| 401 | + // See comment in default clause below. | |
| 402 | + var buf bytes.Buffer | |
| 403 | + fmt.Fprint(&buf, arg) | |
| 404 | + return c.writeBytes(buf.Bytes()) | |
| 405 | + default: | |
| 406 | + // This default clause is intended to handle builtin numeric types. | |
| 407 | + // The function should return an error for other types, but this is not | |
| 408 | + // done for compatibility with previous versions of the package. | |
| 409 | + var buf bytes.Buffer | |
| 410 | + fmt.Fprint(&buf, arg) | |
| 411 | + return c.writeBytes(buf.Bytes()) | |
| 412 | + } | |
| 413 | +} | |
| 414 | + | |
| 415 | +type protocolError string | |
| 416 | + | |
| 417 | +func (pe protocolError) Error() string { | |
| 418 | + return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe)) | |
| 419 | +} | |
| 420 | + | |
| 421 | +func (c *conn) readLine() ([]byte, error) { | |
| 422 | + p, err := c.br.ReadSlice('\n') | |
| 423 | + if err == bufio.ErrBufferFull { | |
| 424 | + return nil, protocolError("long response line") | |
| 425 | + } | |
| 426 | + if err != nil { | |
| 427 | + return nil, err | |
| 428 | + } | |
| 429 | + i := len(p) - 2 | |
| 430 | + if i < 0 || p[i] != '\r' { | |
| 431 | + return nil, protocolError("bad response line terminator") | |
| 432 | + } | |
| 433 | + return p[:i], nil | |
| 434 | +} | |
| 435 | + | |
| 436 | +// parseLen parses bulk string and array lengths. | |
| 437 | +func parseLen(p []byte) (int, error) { | |
| 438 | + if len(p) == 0 { | |
| 439 | + return -1, protocolError("malformed length") | |
| 440 | + } | |
| 441 | + | |
| 442 | + if p[0] == '-' && len(p) == 2 && p[1] == '1' { | |
| 443 | + // handle $-1 and $-1 null replies. | |
| 444 | + return -1, nil | |
| 445 | + } | |
| 446 | + | |
| 447 | + var n int | |
| 448 | + for _, b := range p { | |
| 449 | + n *= 10 | |
| 450 | + if b < '0' || b > '9' { | |
| 451 | + return -1, protocolError("illegal bytes in length") | |
| 452 | + } | |
| 453 | + n += int(b - '0') | |
| 454 | + } | |
| 455 | + | |
| 456 | + return n, nil | |
| 457 | +} | |
| 458 | + | |
| 459 | +// parseInt parses an integer reply. | |
| 460 | +func parseInt(p []byte) (interface{}, error) { | |
| 461 | + if len(p) == 0 { | |
| 462 | + return 0, protocolError("malformed integer") | |
| 463 | + } | |
| 464 | + | |
| 465 | + var negate bool | |
| 466 | + if p[0] == '-' { | |
| 467 | + negate = true | |
| 468 | + p = p[1:] | |
| 469 | + if len(p) == 0 { | |
| 470 | + return 0, protocolError("malformed integer") | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + var n int64 | |
| 475 | + for _, b := range p { | |
| 476 | + n *= 10 | |
| 477 | + if b < '0' || b > '9' { | |
| 478 | + return 0, protocolError("illegal bytes in length") | |
| 479 | + } | |
| 480 | + n += int64(b - '0') | |
| 481 | + } | |
| 482 | + | |
| 483 | + if negate { | |
| 484 | + n = -n | |
| 485 | + } | |
| 486 | + return n, nil | |
| 487 | +} | |
| 488 | + | |
| 489 | +var ( | |
| 490 | + okReply interface{} = "OK" | |
| 491 | + pongReply interface{} = "PONG" | |
| 492 | +) | |
| 493 | + | |
| 494 | +func (c *conn) readReply() (interface{}, error) { | |
| 495 | + line, err := c.readLine() | |
| 496 | + if err != nil { | |
| 497 | + return nil, err | |
| 498 | + } | |
| 499 | + if len(line) == 0 { | |
| 500 | + return nil, protocolError("short response line") | |
| 501 | + } | |
| 502 | + switch line[0] { | |
| 503 | + case '+': | |
| 504 | + switch { | |
| 505 | + case len(line) == 3 && line[1] == 'O' && line[2] == 'K': | |
| 506 | + // Avoid allocation for frequent "+OK" response. | |
| 507 | + return okReply, nil | |
| 508 | + case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G': | |
| 509 | + // Avoid allocation in PING command benchmarks :) | |
| 510 | + return pongReply, nil | |
| 511 | + default: | |
| 512 | + return string(line[1:]), nil | |
| 513 | + } | |
| 514 | + case '-': | |
| 515 | + return Error(string(line[1:])), nil | |
| 516 | + case ':': | |
| 517 | + return parseInt(line[1:]) | |
| 518 | + case '$': | |
| 519 | + n, err := parseLen(line[1:]) | |
| 520 | + if n < 0 || err != nil { | |
| 521 | + return nil, err | |
| 522 | + } | |
| 523 | + p := make([]byte, n) | |
| 524 | + _, err = io.ReadFull(c.br, p) | |
| 525 | + if err != nil { | |
| 526 | + return nil, err | |
| 527 | + } | |
| 528 | + if line, err := c.readLine(); err != nil { | |
| 529 | + return nil, err | |
| 530 | + } else if len(line) != 0 { | |
| 531 | + return nil, protocolError("bad bulk string format") | |
| 532 | + } | |
| 533 | + return p, nil | |
| 534 | + case '*': | |
| 535 | + n, err := parseLen(line[1:]) | |
| 536 | + if n < 0 || err != nil { | |
| 537 | + return nil, err | |
| 538 | + } | |
| 539 | + r := make([]interface{}, n) | |
| 540 | + for i := range r { | |
| 541 | + r[i], err = c.readReply() | |
| 542 | + if err != nil { | |
| 543 | + return nil, err | |
| 544 | + } | |
| 545 | + } | |
| 546 | + return r, nil | |
| 547 | + } | |
| 548 | + return nil, protocolError("unexpected response line") | |
| 549 | +} | |
| 550 | + | |
| 551 | +func (c *conn) Send(cmd string, args ...interface{}) error { | |
| 552 | + c.mu.Lock() | |
| 553 | + c.pending += 1 | |
| 554 | + c.mu.Unlock() | |
| 555 | + if c.writeTimeout != 0 { | |
| 556 | + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | |
| 557 | + } | |
| 558 | + if err := c.writeCommand(cmd, args); err != nil { | |
| 559 | + return c.fatal(err) | |
| 560 | + } | |
| 561 | + return nil | |
| 562 | +} | |
| 563 | + | |
| 564 | +func (c *conn) Flush() error { | |
| 565 | + if c.writeTimeout != 0 { | |
| 566 | + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | |
| 567 | + } | |
| 568 | + if err := c.bw.Flush(); err != nil { | |
| 569 | + return c.fatal(err) | |
| 570 | + } | |
| 571 | + return nil | |
| 572 | +} | |
| 573 | + | |
| 574 | +func (c *conn) Receive() (reply interface{}, err error) { | |
| 575 | + if c.readTimeout != 0 { | |
| 576 | + c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) | |
| 577 | + } | |
| 578 | + if reply, err = c.readReply(); err != nil { | |
| 579 | + return nil, c.fatal(err) | |
| 580 | + } | |
| 581 | + // When using pub/sub, the number of receives can be greater than the | |
| 582 | + // number of sends. To enable normal use of the connection after | |
| 583 | + // unsubscribing from all channels, we do not decrement pending to a | |
| 584 | + // negative value. | |
| 585 | + // | |
| 586 | + // The pending field is decremented after the reply is read to handle the | |
| 587 | + // case where Receive is called before Send. | |
| 588 | + c.mu.Lock() | |
| 589 | + if c.pending > 0 { | |
| 590 | + c.pending -= 1 | |
| 591 | + } | |
| 592 | + c.mu.Unlock() | |
| 593 | + if err, ok := reply.(Error); ok { | |
| 594 | + return nil, err | |
| 595 | + } | |
| 596 | + return | |
| 597 | +} | |
| 598 | + | |
| 599 | +func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) { | |
| 600 | + c.mu.Lock() | |
| 601 | + pending := c.pending | |
| 602 | + c.pending = 0 | |
| 603 | + c.mu.Unlock() | |
| 604 | + | |
| 605 | + if cmd == "" && pending == 0 { | |
| 606 | + return nil, nil | |
| 607 | + } | |
| 608 | + | |
| 609 | + if c.writeTimeout != 0 { | |
| 610 | + c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout)) | |
| 611 | + } | |
| 612 | + | |
| 613 | + if cmd != "" { | |
| 614 | + if err := c.writeCommand(cmd, args); err != nil { | |
| 615 | + return nil, c.fatal(err) | |
| 616 | + } | |
| 617 | + } | |
| 618 | + | |
| 619 | + if err := c.bw.Flush(); err != nil { | |
| 620 | + return nil, c.fatal(err) | |
| 621 | + } | |
| 622 | + | |
| 623 | + if c.readTimeout != 0 { | |
| 624 | + c.conn.SetReadDeadline(time.Now().Add(c.readTimeout)) | |
| 625 | + } | |
| 626 | + | |
| 627 | + if cmd == "" { | |
| 628 | + reply := make([]interface{}, pending) | |
| 629 | + for i := range reply { | |
| 630 | + r, e := c.readReply() | |
| 631 | + if e != nil { | |
| 632 | + return nil, c.fatal(e) | |
| 633 | + } | |
| 634 | + reply[i] = r | |
| 635 | + } | |
| 636 | + return reply, nil | |
| 637 | + } | |
| 638 | + | |
| 639 | + var err error | |
| 640 | + var reply interface{} | |
| 641 | + for i := 0; i <= pending; i++ { | |
| 642 | + var e error | |
| 643 | + if reply, e = c.readReply(); e != nil { | |
| 644 | + return nil, c.fatal(e) | |
| 645 | + } | |
| 646 | + if e, ok := reply.(Error); ok && err == nil { | |
| 647 | + err = e | |
| 648 | + } | |
| 649 | + } | |
| 650 | + return reply, err | |
| 651 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,823 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "bytes" | |
| 19 | + "crypto/tls" | |
| 20 | + "crypto/x509" | |
| 21 | + "fmt" | |
| 22 | + "io" | |
| 23 | + "math" | |
| 24 | + "net" | |
| 25 | + "os" | |
| 26 | + "reflect" | |
| 27 | + "strings" | |
| 28 | + "testing" | |
| 29 | + "time" | |
| 30 | + | |
| 31 | + "github.com/garyburd/redigo/redis" | |
| 32 | +) | |
| 33 | + | |
| 34 | +type testConn struct { | |
| 35 | + io.Reader | |
| 36 | + io.Writer | |
| 37 | +} | |
| 38 | + | |
| 39 | +func (*testConn) Close() error { return nil } | |
| 40 | +func (*testConn) LocalAddr() net.Addr { return nil } | |
| 41 | +func (*testConn) RemoteAddr() net.Addr { return nil } | |
| 42 | +func (*testConn) SetDeadline(t time.Time) error { return nil } | |
| 43 | +func (*testConn) SetReadDeadline(t time.Time) error { return nil } | |
| 44 | +func (*testConn) SetWriteDeadline(t time.Time) error { return nil } | |
| 45 | + | |
| 46 | +func dialTestConn(r string, w io.Writer) redis.DialOption { | |
| 47 | + return redis.DialNetDial(func(network, addr string) (net.Conn, error) { | |
| 48 | + return &testConn{Reader: strings.NewReader(r), Writer: w}, nil | |
| 49 | + }) | |
| 50 | +} | |
| 51 | + | |
| 52 | +type tlsTestConn struct { | |
| 53 | + net.Conn | |
| 54 | + done chan struct{} | |
| 55 | +} | |
| 56 | + | |
| 57 | +func (c *tlsTestConn) Close() error { | |
| 58 | + c.Conn.Close() | |
| 59 | + <-c.done | |
| 60 | + return nil | |
| 61 | +} | |
| 62 | + | |
| 63 | +func dialTestConnTLS(r string, w io.Writer) redis.DialOption { | |
| 64 | + return redis.DialNetDial(func(network, addr string) (net.Conn, error) { | |
| 65 | + client, server := net.Pipe() | |
| 66 | + tlsServer := tls.Server(server, &serverTLSConfig) | |
| 67 | + go io.Copy(tlsServer, strings.NewReader(r)) | |
| 68 | + done := make(chan struct{}) | |
| 69 | + go func() { | |
| 70 | + io.Copy(w, tlsServer) | |
| 71 | + close(done) | |
| 72 | + }() | |
| 73 | + return &tlsTestConn{Conn: client, done: done}, nil | |
| 74 | + }) | |
| 75 | +} | |
| 76 | + | |
| 77 | +type durationArg struct { | |
| 78 | + time.Duration | |
| 79 | +} | |
| 80 | + | |
| 81 | +func (t durationArg) RedisArg() interface{} { | |
| 82 | + return t.Seconds() | |
| 83 | +} | |
| 84 | + | |
| 85 | +type recursiveArg int | |
| 86 | + | |
| 87 | +func (v recursiveArg) RedisArg() interface{} { return v } | |
| 88 | + | |
| 89 | +var writeTests = []struct { | |
| 90 | + args []interface{} | |
| 91 | + expected string | |
| 92 | +}{ | |
| 93 | + { | |
| 94 | + []interface{}{"SET", "key", "value"}, | |
| 95 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", | |
| 96 | + }, | |
| 97 | + { | |
| 98 | + []interface{}{"SET", "key", "value"}, | |
| 99 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n", | |
| 100 | + }, | |
| 101 | + { | |
| 102 | + []interface{}{"SET", "key", byte(100)}, | |
| 103 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", | |
| 104 | + }, | |
| 105 | + { | |
| 106 | + []interface{}{"SET", "key", 100}, | |
| 107 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n", | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + []interface{}{"SET", "key", int64(math.MinInt64)}, | |
| 111 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n", | |
| 112 | + }, | |
| 113 | + { | |
| 114 | + []interface{}{"SET", "key", float64(1349673917.939762)}, | |
| 115 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n", | |
| 116 | + }, | |
| 117 | + { | |
| 118 | + []interface{}{"SET", "key", ""}, | |
| 119 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + []interface{}{"SET", "key", nil}, | |
| 123 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n", | |
| 124 | + }, | |
| 125 | + { | |
| 126 | + []interface{}{"SET", "key", durationArg{time.Minute}}, | |
| 127 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$2\r\n60\r\n", | |
| 128 | + }, | |
| 129 | + { | |
| 130 | + []interface{}{"SET", "key", recursiveArg(123)}, | |
| 131 | + "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n123\r\n", | |
| 132 | + }, | |
| 133 | + { | |
| 134 | + []interface{}{"ECHO", true, false}, | |
| 135 | + "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n", | |
| 136 | + }, | |
| 137 | +} | |
| 138 | + | |
| 139 | +func TestWrite(t *testing.T) { | |
| 140 | + for _, tt := range writeTests { | |
| 141 | + var buf bytes.Buffer | |
| 142 | + c, _ := redis.Dial("", "", dialTestConn("", &buf)) | |
| 143 | + err := c.Send(tt.args[0].(string), tt.args[1:]...) | |
| 144 | + if err != nil { | |
| 145 | + t.Errorf("Send(%v) returned error %v", tt.args, err) | |
| 146 | + continue | |
| 147 | + } | |
| 148 | + c.Flush() | |
| 149 | + actual := buf.String() | |
| 150 | + if actual != tt.expected { | |
| 151 | + t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected) | |
| 152 | + } | |
| 153 | + } | |
| 154 | +} | |
| 155 | + | |
| 156 | +var errorSentinel = &struct{}{} | |
| 157 | + | |
| 158 | +var readTests = []struct { | |
| 159 | + reply string | |
| 160 | + expected interface{} | |
| 161 | +}{ | |
| 162 | + { | |
| 163 | + "+OK\r\n", | |
| 164 | + "OK", | |
| 165 | + }, | |
| 166 | + { | |
| 167 | + "+PONG\r\n", | |
| 168 | + "PONG", | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "@OK\r\n", | |
| 172 | + errorSentinel, | |
| 173 | + }, | |
| 174 | + { | |
| 175 | + "$6\r\nfoobar\r\n", | |
| 176 | + []byte("foobar"), | |
| 177 | + }, | |
| 178 | + { | |
| 179 | + "$-1\r\n", | |
| 180 | + nil, | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + ":1\r\n", | |
| 184 | + int64(1), | |
| 185 | + }, | |
| 186 | + { | |
| 187 | + ":-2\r\n", | |
| 188 | + int64(-2), | |
| 189 | + }, | |
| 190 | + { | |
| 191 | + "*0\r\n", | |
| 192 | + []interface{}{}, | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "*-1\r\n", | |
| 196 | + nil, | |
| 197 | + }, | |
| 198 | + { | |
| 199 | + "*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n", | |
| 200 | + []interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")}, | |
| 201 | + }, | |
| 202 | + { | |
| 203 | + "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n", | |
| 204 | + []interface{}{[]byte("foo"), nil, []byte("bar")}, | |
| 205 | + }, | |
| 206 | + | |
| 207 | + { | |
| 208 | + // "x" is not a valid length | |
| 209 | + "$x\r\nfoobar\r\n", | |
| 210 | + errorSentinel, | |
| 211 | + }, | |
| 212 | + { | |
| 213 | + // -2 is not a valid length | |
| 214 | + "$-2\r\n", | |
| 215 | + errorSentinel, | |
| 216 | + }, | |
| 217 | + { | |
| 218 | + // "x" is not a valid integer | |
| 219 | + ":x\r\n", | |
| 220 | + errorSentinel, | |
| 221 | + }, | |
| 222 | + { | |
| 223 | + // missing \r\n following value | |
| 224 | + "$6\r\nfoobar", | |
| 225 | + errorSentinel, | |
| 226 | + }, | |
| 227 | + { | |
| 228 | + // short value | |
| 229 | + "$6\r\nxx", | |
| 230 | + errorSentinel, | |
| 231 | + }, | |
| 232 | + { | |
| 233 | + // long value | |
| 234 | + "$6\r\nfoobarx\r\n", | |
| 235 | + errorSentinel, | |
| 236 | + }, | |
| 237 | +} | |
| 238 | + | |
| 239 | +func TestRead(t *testing.T) { | |
| 240 | + for _, tt := range readTests { | |
| 241 | + c, _ := redis.Dial("", "", dialTestConn(tt.reply, nil)) | |
| 242 | + actual, err := c.Receive() | |
| 243 | + if tt.expected == errorSentinel { | |
| 244 | + if err == nil { | |
| 245 | + t.Errorf("Receive(%q) did not return expected error", tt.reply) | |
| 246 | + } | |
| 247 | + } else { | |
| 248 | + if err != nil { | |
| 249 | + t.Errorf("Receive(%q) returned error %v", tt.reply, err) | |
| 250 | + continue | |
| 251 | + } | |
| 252 | + if !reflect.DeepEqual(actual, tt.expected) { | |
| 253 | + t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected) | |
| 254 | + } | |
| 255 | + } | |
| 256 | + } | |
| 257 | +} | |
| 258 | + | |
| 259 | +var testCommands = []struct { | |
| 260 | + args []interface{} | |
| 261 | + expected interface{} | |
| 262 | +}{ | |
| 263 | + { | |
| 264 | + []interface{}{"PING"}, | |
| 265 | + "PONG", | |
| 266 | + }, | |
| 267 | + { | |
| 268 | + []interface{}{"SET", "foo", "bar"}, | |
| 269 | + "OK", | |
| 270 | + }, | |
| 271 | + { | |
| 272 | + []interface{}{"GET", "foo"}, | |
| 273 | + []byte("bar"), | |
| 274 | + }, | |
| 275 | + { | |
| 276 | + []interface{}{"GET", "nokey"}, | |
| 277 | + nil, | |
| 278 | + }, | |
| 279 | + { | |
| 280 | + []interface{}{"MGET", "nokey", "foo"}, | |
| 281 | + []interface{}{nil, []byte("bar")}, | |
| 282 | + }, | |
| 283 | + { | |
| 284 | + []interface{}{"INCR", "mycounter"}, | |
| 285 | + int64(1), | |
| 286 | + }, | |
| 287 | + { | |
| 288 | + []interface{}{"LPUSH", "mylist", "foo"}, | |
| 289 | + int64(1), | |
| 290 | + }, | |
| 291 | + { | |
| 292 | + []interface{}{"LPUSH", "mylist", "bar"}, | |
| 293 | + int64(2), | |
| 294 | + }, | |
| 295 | + { | |
| 296 | + []interface{}{"LRANGE", "mylist", 0, -1}, | |
| 297 | + []interface{}{[]byte("bar"), []byte("foo")}, | |
| 298 | + }, | |
| 299 | + { | |
| 300 | + []interface{}{"MULTI"}, | |
| 301 | + "OK", | |
| 302 | + }, | |
| 303 | + { | |
| 304 | + []interface{}{"LRANGE", "mylist", 0, -1}, | |
| 305 | + "QUEUED", | |
| 306 | + }, | |
| 307 | + { | |
| 308 | + []interface{}{"PING"}, | |
| 309 | + "QUEUED", | |
| 310 | + }, | |
| 311 | + { | |
| 312 | + []interface{}{"EXEC"}, | |
| 313 | + []interface{}{ | |
| 314 | + []interface{}{[]byte("bar"), []byte("foo")}, | |
| 315 | + "PONG", | |
| 316 | + }, | |
| 317 | + }, | |
| 318 | +} | |
| 319 | + | |
| 320 | +func TestDoCommands(t *testing.T) { | |
| 321 | + c, err := redis.DialDefaultServer() | |
| 322 | + if err != nil { | |
| 323 | + t.Fatalf("error connection to database, %v", err) | |
| 324 | + } | |
| 325 | + defer c.Close() | |
| 326 | + | |
| 327 | + for _, cmd := range testCommands { | |
| 328 | + actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...) | |
| 329 | + if err != nil { | |
| 330 | + t.Errorf("Do(%v) returned error %v", cmd.args, err) | |
| 331 | + continue | |
| 332 | + } | |
| 333 | + if !reflect.DeepEqual(actual, cmd.expected) { | |
| 334 | + t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected) | |
| 335 | + } | |
| 336 | + } | |
| 337 | +} | |
| 338 | + | |
| 339 | +func TestPipelineCommands(t *testing.T) { | |
| 340 | + c, err := redis.DialDefaultServer() | |
| 341 | + if err != nil { | |
| 342 | + t.Fatalf("error connection to database, %v", err) | |
| 343 | + } | |
| 344 | + defer c.Close() | |
| 345 | + | |
| 346 | + for _, cmd := range testCommands { | |
| 347 | + if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { | |
| 348 | + t.Fatalf("Send(%v) returned error %v", cmd.args, err) | |
| 349 | + } | |
| 350 | + } | |
| 351 | + if err := c.Flush(); err != nil { | |
| 352 | + t.Errorf("Flush() returned error %v", err) | |
| 353 | + } | |
| 354 | + for _, cmd := range testCommands { | |
| 355 | + actual, err := c.Receive() | |
| 356 | + if err != nil { | |
| 357 | + t.Fatalf("Receive(%v) returned error %v", cmd.args, err) | |
| 358 | + } | |
| 359 | + if !reflect.DeepEqual(actual, cmd.expected) { | |
| 360 | + t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) | |
| 361 | + } | |
| 362 | + } | |
| 363 | +} | |
| 364 | + | |
| 365 | +func TestBlankCommmand(t *testing.T) { | |
| 366 | + c, err := redis.DialDefaultServer() | |
| 367 | + if err != nil { | |
| 368 | + t.Fatalf("error connection to database, %v", err) | |
| 369 | + } | |
| 370 | + defer c.Close() | |
| 371 | + | |
| 372 | + for _, cmd := range testCommands { | |
| 373 | + if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil { | |
| 374 | + t.Fatalf("Send(%v) returned error %v", cmd.args, err) | |
| 375 | + } | |
| 376 | + } | |
| 377 | + reply, err := redis.Values(c.Do("")) | |
| 378 | + if err != nil { | |
| 379 | + t.Fatalf("Do() returned error %v", err) | |
| 380 | + } | |
| 381 | + if len(reply) != len(testCommands) { | |
| 382 | + t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands)) | |
| 383 | + } | |
| 384 | + for i, cmd := range testCommands { | |
| 385 | + actual := reply[i] | |
| 386 | + if !reflect.DeepEqual(actual, cmd.expected) { | |
| 387 | + t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected) | |
| 388 | + } | |
| 389 | + } | |
| 390 | +} | |
| 391 | + | |
| 392 | +func TestRecvBeforeSend(t *testing.T) { | |
| 393 | + c, err := redis.DialDefaultServer() | |
| 394 | + if err != nil { | |
| 395 | + t.Fatalf("error connection to database, %v", err) | |
| 396 | + } | |
| 397 | + defer c.Close() | |
| 398 | + done := make(chan struct{}) | |
| 399 | + go func() { | |
| 400 | + c.Receive() | |
| 401 | + close(done) | |
| 402 | + }() | |
| 403 | + time.Sleep(time.Millisecond) | |
| 404 | + c.Send("PING") | |
| 405 | + c.Flush() | |
| 406 | + <-done | |
| 407 | + _, err = c.Do("") | |
| 408 | + if err != nil { | |
| 409 | + t.Fatalf("error=%v", err) | |
| 410 | + } | |
| 411 | +} | |
| 412 | + | |
| 413 | +func TestError(t *testing.T) { | |
| 414 | + c, err := redis.DialDefaultServer() | |
| 415 | + if err != nil { | |
| 416 | + t.Fatalf("error connection to database, %v", err) | |
| 417 | + } | |
| 418 | + defer c.Close() | |
| 419 | + | |
| 420 | + c.Do("SET", "key", "val") | |
| 421 | + _, err = c.Do("HSET", "key", "fld", "val") | |
| 422 | + if err == nil { | |
| 423 | + t.Errorf("Expected err for HSET on string key.") | |
| 424 | + } | |
| 425 | + if c.Err() != nil { | |
| 426 | + t.Errorf("Conn has Err()=%v, expect nil", c.Err()) | |
| 427 | + } | |
| 428 | + _, err = c.Do("SET", "key", "val") | |
| 429 | + if err != nil { | |
| 430 | + t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err) | |
| 431 | + } | |
| 432 | +} | |
| 433 | + | |
| 434 | +func TestReadTimeout(t *testing.T) { | |
| 435 | + l, err := net.Listen("tcp", "127.0.0.1:0") | |
| 436 | + if err != nil { | |
| 437 | + t.Fatalf("net.Listen returned %v", err) | |
| 438 | + } | |
| 439 | + defer l.Close() | |
| 440 | + | |
| 441 | + go func() { | |
| 442 | + for { | |
| 443 | + c, err := l.Accept() | |
| 444 | + if err != nil { | |
| 445 | + return | |
| 446 | + } | |
| 447 | + go func() { | |
| 448 | + time.Sleep(time.Second) | |
| 449 | + c.Write([]byte("+OK\r\n")) | |
| 450 | + c.Close() | |
| 451 | + }() | |
| 452 | + } | |
| 453 | + }() | |
| 454 | + | |
| 455 | + // Do | |
| 456 | + | |
| 457 | + c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) | |
| 458 | + if err != nil { | |
| 459 | + t.Fatalf("redis.Dial returned %v", err) | |
| 460 | + } | |
| 461 | + defer c1.Close() | |
| 462 | + | |
| 463 | + _, err = c1.Do("PING") | |
| 464 | + if err == nil { | |
| 465 | + t.Fatalf("c1.Do() returned nil, expect error") | |
| 466 | + } | |
| 467 | + if c1.Err() == nil { | |
| 468 | + t.Fatalf("c1.Err() = nil, expect error") | |
| 469 | + } | |
| 470 | + | |
| 471 | + // Send/Flush/Receive | |
| 472 | + | |
| 473 | + c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond)) | |
| 474 | + if err != nil { | |
| 475 | + t.Fatalf("redis.Dial returned %v", err) | |
| 476 | + } | |
| 477 | + defer c2.Close() | |
| 478 | + | |
| 479 | + c2.Send("PING") | |
| 480 | + c2.Flush() | |
| 481 | + _, err = c2.Receive() | |
| 482 | + if err == nil { | |
| 483 | + t.Fatalf("c2.Receive() returned nil, expect error") | |
| 484 | + } | |
| 485 | + if c2.Err() == nil { | |
| 486 | + t.Fatalf("c2.Err() = nil, expect error") | |
| 487 | + } | |
| 488 | +} | |
| 489 | + | |
| 490 | +var dialErrors = []struct { | |
| 491 | + rawurl string | |
| 492 | + expectedError string | |
| 493 | +}{ | |
| 494 | + { | |
| 495 | + "localhost", | |
| 496 | + "invalid redis URL scheme", | |
| 497 | + }, | |
| 498 | + // The error message for invalid hosts is different in different | |
| 499 | + // versions of Go, so just check that there is an error message. | |
| 500 | + { | |
| 501 | + "redis://weird url", | |
| 502 | + "", | |
| 503 | + }, | |
| 504 | + { | |
| 505 | + "redis://foo:bar:baz", | |
| 506 | + "", | |
| 507 | + }, | |
| 508 | + { | |
| 509 | + "http://www.google.com", | |
| 510 | + "invalid redis URL scheme: http", | |
| 511 | + }, | |
| 512 | + { | |
| 513 | + "redis://localhost:6379/abc123", | |
| 514 | + "invalid database: abc123", | |
| 515 | + }, | |
| 516 | +} | |
| 517 | + | |
| 518 | +func TestDialURLErrors(t *testing.T) { | |
| 519 | + for _, d := range dialErrors { | |
| 520 | + _, err := redis.DialURL(d.rawurl) | |
| 521 | + if err == nil || !strings.Contains(err.Error(), d.expectedError) { | |
| 522 | + t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError) | |
| 523 | + } | |
| 524 | + } | |
| 525 | +} | |
| 526 | + | |
| 527 | +func TestDialURLPort(t *testing.T) { | |
| 528 | + checkPort := func(network, address string) (net.Conn, error) { | |
| 529 | + if address != "localhost:6379" { | |
| 530 | + t.Errorf("DialURL did not set port to 6379 by default (got %v)", address) | |
| 531 | + } | |
| 532 | + return nil, nil | |
| 533 | + } | |
| 534 | + _, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort)) | |
| 535 | + if err != nil { | |
| 536 | + t.Error("dial error:", err) | |
| 537 | + } | |
| 538 | +} | |
| 539 | + | |
| 540 | +func TestDialURLHost(t *testing.T) { | |
| 541 | + checkHost := func(network, address string) (net.Conn, error) { | |
| 542 | + if address != "localhost:6379" { | |
| 543 | + t.Errorf("DialURL did not set host to localhost by default (got %v)", address) | |
| 544 | + } | |
| 545 | + return nil, nil | |
| 546 | + } | |
| 547 | + _, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost)) | |
| 548 | + if err != nil { | |
| 549 | + t.Error("dial error:", err) | |
| 550 | + } | |
| 551 | +} | |
| 552 | + | |
| 553 | +var dialURLTests = []struct { | |
| 554 | + description string | |
| 555 | + url string | |
| 556 | + r string | |
| 557 | + w string | |
| 558 | +}{ | |
| 559 | + {"password", "redis://x:abc123@localhost", "+OK\r\n", "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"}, | |
| 560 | + {"database 3", "redis://localhost/3", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"}, | |
| 561 | + {"database 99", "redis://localhost/99", "+OK\r\n", "*2\r\n$6\r\nSELECT\r\n$2\r\n99\r\n"}, | |
| 562 | + {"no database", "redis://localhost/", "+OK\r\n", ""}, | |
| 563 | +} | |
| 564 | + | |
| 565 | +func TestDialURL(t *testing.T) { | |
| 566 | + for _, tt := range dialURLTests { | |
| 567 | + var buf bytes.Buffer | |
| 568 | + // UseTLS should be ignored in all of these tests. | |
| 569 | + _, err := redis.DialURL(tt.url, dialTestConn(tt.r, &buf), redis.DialUseTLS(true)) | |
| 570 | + if err != nil { | |
| 571 | + t.Errorf("%s dial error: %v", tt.description, err) | |
| 572 | + continue | |
| 573 | + } | |
| 574 | + if w := buf.String(); w != tt.w { | |
| 575 | + t.Errorf("%s commands = %q, want %q", tt.description, w, tt.w) | |
| 576 | + } | |
| 577 | + } | |
| 578 | +} | |
| 579 | + | |
| 580 | +func checkPingPong(t *testing.T, buf *bytes.Buffer, c redis.Conn) { | |
| 581 | + resp, err := c.Do("PING") | |
| 582 | + if err != nil { | |
| 583 | + t.Fatal("ping error:", err) | |
| 584 | + } | |
| 585 | + // Close connection to ensure that writes to buf are complete. | |
| 586 | + c.Close() | |
| 587 | + expected := "*1\r\n$4\r\nPING\r\n" | |
| 588 | + actual := buf.String() | |
| 589 | + if actual != expected { | |
| 590 | + t.Errorf("commands = %q, want %q", actual, expected) | |
| 591 | + } | |
| 592 | + if resp != "PONG" { | |
| 593 | + t.Errorf("resp = %v, want %v", resp, "PONG") | |
| 594 | + } | |
| 595 | +} | |
| 596 | + | |
| 597 | +const pingResponse = "+PONG\r\n" | |
| 598 | + | |
| 599 | +func TestDialURLTLS(t *testing.T) { | |
| 600 | + var buf bytes.Buffer | |
| 601 | + c, err := redis.DialURL("rediss://example.com/", | |
| 602 | + redis.DialTLSConfig(&clientTLSConfig), | |
| 603 | + dialTestConnTLS(pingResponse, &buf)) | |
| 604 | + if err != nil { | |
| 605 | + t.Fatal("dial error:", err) | |
| 606 | + } | |
| 607 | + checkPingPong(t, &buf, c) | |
| 608 | +} | |
| 609 | + | |
| 610 | +func TestDialUseTLS(t *testing.T) { | |
| 611 | + var buf bytes.Buffer | |
| 612 | + c, err := redis.Dial("tcp", "example.com:6379", | |
| 613 | + redis.DialTLSConfig(&clientTLSConfig), | |
| 614 | + dialTestConnTLS(pingResponse, &buf), | |
| 615 | + redis.DialUseTLS(true)) | |
| 616 | + if err != nil { | |
| 617 | + t.Fatal("dial error:", err) | |
| 618 | + } | |
| 619 | + checkPingPong(t, &buf, c) | |
| 620 | +} | |
| 621 | + | |
| 622 | +func TestDialTLSSKipVerify(t *testing.T) { | |
| 623 | + var buf bytes.Buffer | |
| 624 | + c, err := redis.Dial("tcp", "example.com:6379", | |
| 625 | + dialTestConnTLS(pingResponse, &buf), | |
| 626 | + redis.DialTLSSkipVerify(true), | |
| 627 | + redis.DialUseTLS(true)) | |
| 628 | + if err != nil { | |
| 629 | + t.Fatal("dial error:", err) | |
| 630 | + } | |
| 631 | + checkPingPong(t, &buf, c) | |
| 632 | +} | |
| 633 | + | |
| 634 | +// Connect to local instance of Redis running on the default port. | |
| 635 | +func ExampleDial() { | |
| 636 | + c, err := redis.Dial("tcp", ":6379") | |
| 637 | + if err != nil { | |
| 638 | + // handle error | |
| 639 | + } | |
| 640 | + defer c.Close() | |
| 641 | +} | |
| 642 | + | |
| 643 | +// Connect to remote instance of Redis using a URL. | |
| 644 | +func ExampleDialURL() { | |
| 645 | + c, err := redis.DialURL(os.Getenv("REDIS_URL")) | |
| 646 | + if err != nil { | |
| 647 | + // handle connection error | |
| 648 | + } | |
| 649 | + defer c.Close() | |
| 650 | +} | |
| 651 | + | |
| 652 | +// TextExecError tests handling of errors in a transaction. See | |
| 653 | +// http://redis.io/topics/transactions for information on how Redis handles | |
| 654 | +// errors in a transaction. | |
| 655 | +func TestExecError(t *testing.T) { | |
| 656 | + c, err := redis.DialDefaultServer() | |
| 657 | + if err != nil { | |
| 658 | + t.Fatalf("error connection to database, %v", err) | |
| 659 | + } | |
| 660 | + defer c.Close() | |
| 661 | + | |
| 662 | + // Execute commands that fail before EXEC is called. | |
| 663 | + | |
| 664 | + c.Do("DEL", "k0") | |
| 665 | + c.Do("ZADD", "k0", 0, 0) | |
| 666 | + c.Send("MULTI") | |
| 667 | + c.Send("NOTACOMMAND", "k0", 0, 0) | |
| 668 | + c.Send("ZINCRBY", "k0", 0, 0) | |
| 669 | + v, err := c.Do("EXEC") | |
| 670 | + if err == nil { | |
| 671 | + t.Fatalf("EXEC returned values %v, expected error", v) | |
| 672 | + } | |
| 673 | + | |
| 674 | + // Execute commands that fail after EXEC is called. The first command | |
| 675 | + // returns an error. | |
| 676 | + | |
| 677 | + c.Do("DEL", "k1") | |
| 678 | + c.Do("ZADD", "k1", 0, 0) | |
| 679 | + c.Send("MULTI") | |
| 680 | + c.Send("HSET", "k1", 0, 0) | |
| 681 | + c.Send("ZINCRBY", "k1", 0, 0) | |
| 682 | + v, err = c.Do("EXEC") | |
| 683 | + if err != nil { | |
| 684 | + t.Fatalf("EXEC returned error %v", err) | |
| 685 | + } | |
| 686 | + | |
| 687 | + vs, err := redis.Values(v, nil) | |
| 688 | + if err != nil { | |
| 689 | + t.Fatalf("Values(v) returned error %v", err) | |
| 690 | + } | |
| 691 | + | |
| 692 | + if len(vs) != 2 { | |
| 693 | + t.Fatalf("len(vs) == %d, want 2", len(vs)) | |
| 694 | + } | |
| 695 | + | |
| 696 | + if _, ok := vs[0].(error); !ok { | |
| 697 | + t.Fatalf("first result is type %T, expected error", vs[0]) | |
| 698 | + } | |
| 699 | + | |
| 700 | + if _, ok := vs[1].([]byte); !ok { | |
| 701 | + t.Fatalf("second result is type %T, expected []byte", vs[1]) | |
| 702 | + } | |
| 703 | + | |
| 704 | + // Execute commands that fail after EXEC is called. The second command | |
| 705 | + // returns an error. | |
| 706 | + | |
| 707 | + c.Do("ZADD", "k2", 0, 0) | |
| 708 | + c.Send("MULTI") | |
| 709 | + c.Send("ZINCRBY", "k2", 0, 0) | |
| 710 | + c.Send("HSET", "k2", 0, 0) | |
| 711 | + v, err = c.Do("EXEC") | |
| 712 | + if err != nil { | |
| 713 | + t.Fatalf("EXEC returned error %v", err) | |
| 714 | + } | |
| 715 | + | |
| 716 | + vs, err = redis.Values(v, nil) | |
| 717 | + if err != nil { | |
| 718 | + t.Fatalf("Values(v) returned error %v", err) | |
| 719 | + } | |
| 720 | + | |
| 721 | + if len(vs) != 2 { | |
| 722 | + t.Fatalf("len(vs) == %d, want 2", len(vs)) | |
| 723 | + } | |
| 724 | + | |
| 725 | + if _, ok := vs[0].([]byte); !ok { | |
| 726 | + t.Fatalf("first result is type %T, expected []byte", vs[0]) | |
| 727 | + } | |
| 728 | + | |
| 729 | + if _, ok := vs[1].(error); !ok { | |
| 730 | + t.Fatalf("second result is type %T, expected error", vs[2]) | |
| 731 | + } | |
| 732 | +} | |
| 733 | + | |
| 734 | +func BenchmarkDoEmpty(b *testing.B) { | |
| 735 | + b.StopTimer() | |
| 736 | + c, err := redis.DialDefaultServer() | |
| 737 | + if err != nil { | |
| 738 | + b.Fatal(err) | |
| 739 | + } | |
| 740 | + defer c.Close() | |
| 741 | + b.StartTimer() | |
| 742 | + for i := 0; i < b.N; i++ { | |
| 743 | + if _, err := c.Do(""); err != nil { | |
| 744 | + b.Fatal(err) | |
| 745 | + } | |
| 746 | + } | |
| 747 | +} | |
| 748 | + | |
| 749 | +func BenchmarkDoPing(b *testing.B) { | |
| 750 | + b.StopTimer() | |
| 751 | + c, err := redis.DialDefaultServer() | |
| 752 | + if err != nil { | |
| 753 | + b.Fatal(err) | |
| 754 | + } | |
| 755 | + defer c.Close() | |
| 756 | + b.StartTimer() | |
| 757 | + for i := 0; i < b.N; i++ { | |
| 758 | + if _, err := c.Do("PING"); err != nil { | |
| 759 | + b.Fatal(err) | |
| 760 | + } | |
| 761 | + } | |
| 762 | +} | |
| 763 | + | |
| 764 | +var clientTLSConfig, serverTLSConfig tls.Config | |
| 765 | + | |
| 766 | +func init() { | |
| 767 | + // The certificate and key for testing TLS dial options was created | |
| 768 | + // using the command | |
| 769 | + // | |
| 770 | + // go run GOROOT/src/crypto/tls/generate_cert.go \ | |
| 771 | + // --rsa-bits 1024 \ | |
| 772 | + // --host 127.0.0.1,::1,example.com --ca \ | |
| 773 | + // --start-date "Jan 1 00:00:00 1970" \ | |
| 774 | + // --duration=1000000h | |
| 775 | + // | |
| 776 | + // where GOROOT is the value of GOROOT reported by go env. | |
| 777 | + localhostCert := []byte(` | |
| 778 | +-----BEGIN CERTIFICATE----- | |
| 779 | +MIICFDCCAX2gAwIBAgIRAJfBL4CUxkXcdlFurb3K+iowDQYJKoZIhvcNAQELBQAw | |
| 780 | +EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2 | |
| 781 | +MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw | |
| 782 | +gYkCgYEArizw8WxMUQ3bGHLeuJ4fDrEpy+L2pqrbYRlKk1DasJ/VkB8bImzIpe6+ | |
| 783 | +LGjiYIxvnDCOJ3f3QplcQuiuMyl6f2irJlJsbFT8Lo/3obnuTKAIaqUdJUqBg6y+ | |
| 784 | +JaL8Auk97FvunfKFv8U1AIhgiLzAfQ/3Eaq1yi87Ra6pMjGbTtcCAwEAAaNoMGYw | |
| 785 | +DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF | |
| 786 | +MAMBAf8wLgYDVR0RBCcwJYILZXhhbXBsZS5jb22HBH8AAAGHEAAAAAAAAAAAAAAA | |
| 787 | +AAAAAAEwDQYJKoZIhvcNAQELBQADgYEAdZ8daIVkyhVwflt5I19m0oq1TycbGO1+ | |
| 788 | +ach7T6cZiBQeNR/SJtxr/wKPEpmvUgbv2BfFrKJ8QoIHYsbNSURTWSEa02pfw4k9 | |
| 789 | +6RQhij3ZkG79Ituj5OYRORV6Z0HUW32r670BtcuHuAhq7YA6Nxy4FtSt7bAlVdRt | |
| 790 | +rrKgNsltzMk= | |
| 791 | +-----END CERTIFICATE-----`) | |
| 792 | + | |
| 793 | + localhostKey := []byte(` | |
| 794 | +-----BEGIN RSA PRIVATE KEY----- | |
| 795 | +MIICXAIBAAKBgQCuLPDxbExRDdsYct64nh8OsSnL4vamqtthGUqTUNqwn9WQHxsi | |
| 796 | +bMil7r4saOJgjG+cMI4nd/dCmVxC6K4zKXp/aKsmUmxsVPwuj/ehue5MoAhqpR0l | |
| 797 | +SoGDrL4lovwC6T3sW+6d8oW/xTUAiGCIvMB9D/cRqrXKLztFrqkyMZtO1wIDAQAB | |
| 798 | +AoGACrc5G6FOEK6JjDeE/Fa+EmlT6PdNtXNNi+vCas3Opo8u1G8VfEi1D4BgstrB | |
| 799 | +Eq+RLkrOdB8tVyuYQYWPMhabMqF+hhKJN72j0OwfuPlVvTInwb/cKjo/zbH1IA+Y | |
| 800 | +HenHNK4ywv7/p/9/MvQPJ3I32cQBCgGUW5chVSH5M1sj5gECQQDabQAI1X0uDqCm | |
| 801 | +KbX9gXVkAgxkFddrt6LBHt57xujFcqEKFE7nwKhDh7DweVs/VEJ+kpid4z+UnLOw | |
| 802 | +KjtP9JolAkEAzCNBphQ//IsbH5rNs10wIUw3Ks/Oepicvr6kUFbIv+neRzi1iJHa | |
| 803 | +m6H7EayK3PWgax6BAsR/t0Jc9XV7r2muSwJAVzN09BHnK+ADGtNEKLTqXMbEk6B0 | |
| 804 | +pDhn7ZmZUOkUPN+Kky+QYM11X6Bob1jDqQDGmymDbGUxGO+GfSofC8inUQJAGfci | |
| 805 | +Eo3g1a6b9JksMPRZeuLG4ZstGErxJRH6tH1Va5PDwitka8qhk8o2tTjNMO3NSdLH | |
| 806 | +diKoXBcE2/Pll5pJoQJBAIMiiMIzXJhnN4mX8may44J/HvMlMf2xuVH2gNMwmZuc | |
| 807 | +Bjqn3yoLHaoZVvbWOi0C2TCN4FjXjaLNZGifQPbIcaA= | |
| 808 | +-----END RSA PRIVATE KEY-----`) | |
| 809 | + | |
| 810 | + cert, err := tls.X509KeyPair(localhostCert, localhostKey) | |
| 811 | + if err != nil { | |
| 812 | + panic(fmt.Sprintf("error creating key pair: %v", err)) | |
| 813 | + } | |
| 814 | + serverTLSConfig.Certificates = []tls.Certificate{cert} | |
| 815 | + | |
| 816 | + certificate, err := x509.ParseCertificate(serverTLSConfig.Certificates[0].Certificate[0]) | |
| 817 | + if err != nil { | |
| 818 | + panic(fmt.Sprintf("error parsing x509 certificate: %v", err)) | |
| 819 | + } | |
| 820 | + | |
| 821 | + clientTLSConfig.RootCAs = x509.NewCertPool() | |
| 822 | + clientTLSConfig.RootCAs.AddCert(certificate) | |
| 823 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,177 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +// Package redis is a client for the Redis database. | |
| 16 | +// | |
| 17 | +// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more | |
| 18 | +// documentation about this package. | |
| 19 | +// | |
| 20 | +// Connections | |
| 21 | +// | |
| 22 | +// The Conn interface is the primary interface for working with Redis. | |
| 23 | +// Applications create connections by calling the Dial, DialWithTimeout or | |
| 24 | +// NewConn functions. In the future, functions will be added for creating | |
| 25 | +// sharded and other types of connections. | |
| 26 | +// | |
| 27 | +// The application must call the connection Close method when the application | |
| 28 | +// is done with the connection. | |
| 29 | +// | |
| 30 | +// Executing Commands | |
| 31 | +// | |
| 32 | +// The Conn interface has a generic method for executing Redis commands: | |
| 33 | +// | |
| 34 | +// Do(commandName string, args ...interface{}) (reply interface{}, err error) | |
| 35 | +// | |
| 36 | +// The Redis command reference (http://redis.io/commands) lists the available | |
| 37 | +// commands. An example of using the Redis APPEND command is: | |
| 38 | +// | |
| 39 | +// n, err := conn.Do("APPEND", "key", "value") | |
| 40 | +// | |
| 41 | +// The Do method converts command arguments to bulk strings for transmission | |
| 42 | +// to the server as follows: | |
| 43 | +// | |
| 44 | +// Go Type Conversion | |
| 45 | +// []byte Sent as is | |
| 46 | +// string Sent as is | |
| 47 | +// int, int64 strconv.FormatInt(v) | |
| 48 | +// float64 strconv.FormatFloat(v, 'g', -1, 64) | |
| 49 | +// bool true -> "1", false -> "0" | |
| 50 | +// nil "" | |
| 51 | +// all other types fmt.Fprint(w, v) | |
| 52 | +// | |
| 53 | +// Redis command reply types are represented using the following Go types: | |
| 54 | +// | |
| 55 | +// Redis type Go type | |
| 56 | +// error redis.Error | |
| 57 | +// integer int64 | |
| 58 | +// simple string string | |
| 59 | +// bulk string []byte or nil if value not present. | |
| 60 | +// array []interface{} or nil if value not present. | |
| 61 | +// | |
| 62 | +// Use type assertions or the reply helper functions to convert from | |
| 63 | +// interface{} to the specific Go type for the command result. | |
| 64 | +// | |
| 65 | +// Pipelining | |
| 66 | +// | |
| 67 | +// Connections support pipelining using the Send, Flush and Receive methods. | |
| 68 | +// | |
| 69 | +// Send(commandName string, args ...interface{}) error | |
| 70 | +// Flush() error | |
| 71 | +// Receive() (reply interface{}, err error) | |
| 72 | +// | |
| 73 | +// Send writes the command to the connection's output buffer. Flush flushes the | |
| 74 | +// connection's output buffer to the server. Receive reads a single reply from | |
| 75 | +// the server. The following example shows a simple pipeline. | |
| 76 | +// | |
| 77 | +// c.Send("SET", "foo", "bar") | |
| 78 | +// c.Send("GET", "foo") | |
| 79 | +// c.Flush() | |
| 80 | +// c.Receive() // reply from SET | |
| 81 | +// v, err = c.Receive() // reply from GET | |
| 82 | +// | |
| 83 | +// The Do method combines the functionality of the Send, Flush and Receive | |
| 84 | +// methods. The Do method starts by writing the command and flushing the output | |
| 85 | +// buffer. Next, the Do method receives all pending replies including the reply | |
| 86 | +// for the command just sent by Do. If any of the received replies is an error, | |
| 87 | +// then Do returns the error. If there are no errors, then Do returns the last | |
| 88 | +// reply. If the command argument to the Do method is "", then the Do method | |
| 89 | +// will flush the output buffer and receive pending replies without sending a | |
| 90 | +// command. | |
| 91 | +// | |
| 92 | +// Use the Send and Do methods to implement pipelined transactions. | |
| 93 | +// | |
| 94 | +// c.Send("MULTI") | |
| 95 | +// c.Send("INCR", "foo") | |
| 96 | +// c.Send("INCR", "bar") | |
| 97 | +// r, err := c.Do("EXEC") | |
| 98 | +// fmt.Println(r) // prints [1, 1] | |
| 99 | +// | |
| 100 | +// Concurrency | |
| 101 | +// | |
| 102 | +// Connections support one concurrent caller to the Receive method and one | |
| 103 | +// concurrent caller to the Send and Flush methods. No other concurrency is | |
| 104 | +// supported including concurrent calls to the Do method. | |
| 105 | +// | |
| 106 | +// For full concurrent access to Redis, use the thread-safe Pool to get, use | |
| 107 | +// and release a connection from within a goroutine. Connections returned from | |
| 108 | +// a Pool have the concurrency restrictions described in the previous | |
| 109 | +// paragraph. | |
| 110 | +// | |
| 111 | +// Publish and Subscribe | |
| 112 | +// | |
| 113 | +// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers. | |
| 114 | +// | |
| 115 | +// c.Send("SUBSCRIBE", "example") | |
| 116 | +// c.Flush() | |
| 117 | +// for { | |
| 118 | +// reply, err := c.Receive() | |
| 119 | +// if err != nil { | |
| 120 | +// return err | |
| 121 | +// } | |
| 122 | +// // process pushed message | |
| 123 | +// } | |
| 124 | +// | |
| 125 | +// The PubSubConn type wraps a Conn with convenience methods for implementing | |
| 126 | +// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods | |
| 127 | +// send and flush a subscription management command. The receive method | |
| 128 | +// converts a pushed message to convenient types for use in a type switch. | |
| 129 | +// | |
| 130 | +// psc := redis.PubSubConn{Conn: c} | |
| 131 | +// psc.Subscribe("example") | |
| 132 | +// for { | |
| 133 | +// switch v := psc.Receive().(type) { | |
| 134 | +// case redis.Message: | |
| 135 | +// fmt.Printf("%s: message: %s\n", v.Channel, v.Data) | |
| 136 | +// case redis.Subscription: | |
| 137 | +// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count) | |
| 138 | +// case error: | |
| 139 | +// return v | |
| 140 | +// } | |
| 141 | +// } | |
| 142 | +// | |
| 143 | +// Reply Helpers | |
| 144 | +// | |
| 145 | +// The Bool, Int, Bytes, String, Strings and Values functions convert a reply | |
| 146 | +// to a value of a specific type. To allow convenient wrapping of calls to the | |
| 147 | +// connection Do and Receive methods, the functions take a second argument of | |
| 148 | +// type error. If the error is non-nil, then the helper function returns the | |
| 149 | +// error. If the error is nil, the function converts the reply to the specified | |
| 150 | +// type: | |
| 151 | +// | |
| 152 | +// exists, err := redis.Bool(c.Do("EXISTS", "foo")) | |
| 153 | +// if err != nil { | |
| 154 | +// // handle error return from c.Do or type conversion error. | |
| 155 | +// } | |
| 156 | +// | |
| 157 | +// The Scan function converts elements of a array reply to Go types: | |
| 158 | +// | |
| 159 | +// var value1 int | |
| 160 | +// var value2 string | |
| 161 | +// reply, err := redis.Values(c.Do("MGET", "key1", "key2")) | |
| 162 | +// if err != nil { | |
| 163 | +// // handle error | |
| 164 | +// } | |
| 165 | +// if _, err := redis.Scan(reply, &value1, &value2); err != nil { | |
| 166 | +// // handle error | |
| 167 | +// } | |
| 168 | +// | |
| 169 | +// Errors | |
| 170 | +// | |
| 171 | +// Connection methods return error replies from the server as type redis.Error. | |
| 172 | +// | |
| 173 | +// Call the connection Err() method to determine if the connection encountered | |
| 174 | +// non-recoverable error such as a network error or protocol parsing error. If | |
| 175 | +// Err() returns a non-nil value, then the connection is not usable and should | |
| 176 | +// be closed. | |
| 177 | +package redis // import "github.com/garyburd/redigo/redis" | ... | ... |
| ... | ... | @@ -0,0 +1,33 @@ |
| 1 | +// +build go1.7 | |
| 2 | + | |
| 3 | +package redis | |
| 4 | + | |
| 5 | +import "crypto/tls" | |
| 6 | + | |
| 7 | +// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case | |
| 8 | +func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { | |
| 9 | + if cfg == nil { | |
| 10 | + return &tls.Config{InsecureSkipVerify: skipVerify} | |
| 11 | + } | |
| 12 | + return &tls.Config{ | |
| 13 | + Rand: cfg.Rand, | |
| 14 | + Time: cfg.Time, | |
| 15 | + Certificates: cfg.Certificates, | |
| 16 | + NameToCertificate: cfg.NameToCertificate, | |
| 17 | + GetCertificate: cfg.GetCertificate, | |
| 18 | + RootCAs: cfg.RootCAs, | |
| 19 | + NextProtos: cfg.NextProtos, | |
| 20 | + ServerName: cfg.ServerName, | |
| 21 | + ClientAuth: cfg.ClientAuth, | |
| 22 | + ClientCAs: cfg.ClientCAs, | |
| 23 | + InsecureSkipVerify: cfg.InsecureSkipVerify, | |
| 24 | + CipherSuites: cfg.CipherSuites, | |
| 25 | + PreferServerCipherSuites: cfg.PreferServerCipherSuites, | |
| 26 | + ClientSessionCache: cfg.ClientSessionCache, | |
| 27 | + MinVersion: cfg.MinVersion, | |
| 28 | + MaxVersion: cfg.MaxVersion, | |
| 29 | + CurvePreferences: cfg.CurvePreferences, | |
| 30 | + DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled, | |
| 31 | + Renegotiation: cfg.Renegotiation, | |
| 32 | + } | |
| 33 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,117 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "bytes" | |
| 19 | + "fmt" | |
| 20 | + "log" | |
| 21 | +) | |
| 22 | + | |
| 23 | +// NewLoggingConn returns a logging wrapper around a connection. | |
| 24 | +func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn { | |
| 25 | + if prefix != "" { | |
| 26 | + prefix = prefix + "." | |
| 27 | + } | |
| 28 | + return &loggingConn{conn, logger, prefix} | |
| 29 | +} | |
| 30 | + | |
| 31 | +type loggingConn struct { | |
| 32 | + Conn | |
| 33 | + logger *log.Logger | |
| 34 | + prefix string | |
| 35 | +} | |
| 36 | + | |
| 37 | +func (c *loggingConn) Close() error { | |
| 38 | + err := c.Conn.Close() | |
| 39 | + var buf bytes.Buffer | |
| 40 | + fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err) | |
| 41 | + c.logger.Output(2, buf.String()) | |
| 42 | + return err | |
| 43 | +} | |
| 44 | + | |
| 45 | +func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) { | |
| 46 | + const chop = 32 | |
| 47 | + switch v := v.(type) { | |
| 48 | + case []byte: | |
| 49 | + if len(v) > chop { | |
| 50 | + fmt.Fprintf(buf, "%q...", v[:chop]) | |
| 51 | + } else { | |
| 52 | + fmt.Fprintf(buf, "%q", v) | |
| 53 | + } | |
| 54 | + case string: | |
| 55 | + if len(v) > chop { | |
| 56 | + fmt.Fprintf(buf, "%q...", v[:chop]) | |
| 57 | + } else { | |
| 58 | + fmt.Fprintf(buf, "%q", v) | |
| 59 | + } | |
| 60 | + case []interface{}: | |
| 61 | + if len(v) == 0 { | |
| 62 | + buf.WriteString("[]") | |
| 63 | + } else { | |
| 64 | + sep := "[" | |
| 65 | + fin := "]" | |
| 66 | + if len(v) > chop { | |
| 67 | + v = v[:chop] | |
| 68 | + fin = "...]" | |
| 69 | + } | |
| 70 | + for _, vv := range v { | |
| 71 | + buf.WriteString(sep) | |
| 72 | + c.printValue(buf, vv) | |
| 73 | + sep = ", " | |
| 74 | + } | |
| 75 | + buf.WriteString(fin) | |
| 76 | + } | |
| 77 | + default: | |
| 78 | + fmt.Fprint(buf, v) | |
| 79 | + } | |
| 80 | +} | |
| 81 | + | |
| 82 | +func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) { | |
| 83 | + var buf bytes.Buffer | |
| 84 | + fmt.Fprintf(&buf, "%s%s(", c.prefix, method) | |
| 85 | + if method != "Receive" { | |
| 86 | + buf.WriteString(commandName) | |
| 87 | + for _, arg := range args { | |
| 88 | + buf.WriteString(", ") | |
| 89 | + c.printValue(&buf, arg) | |
| 90 | + } | |
| 91 | + } | |
| 92 | + buf.WriteString(") -> (") | |
| 93 | + if method != "Send" { | |
| 94 | + c.printValue(&buf, reply) | |
| 95 | + buf.WriteString(", ") | |
| 96 | + } | |
| 97 | + fmt.Fprintf(&buf, "%v)", err) | |
| 98 | + c.logger.Output(3, buf.String()) | |
| 99 | +} | |
| 100 | + | |
| 101 | +func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) { | |
| 102 | + reply, err := c.Conn.Do(commandName, args...) | |
| 103 | + c.print("Do", commandName, args, reply, err) | |
| 104 | + return reply, err | |
| 105 | +} | |
| 106 | + | |
| 107 | +func (c *loggingConn) Send(commandName string, args ...interface{}) error { | |
| 108 | + err := c.Conn.Send(commandName, args...) | |
| 109 | + c.print("Send", commandName, args, nil, err) | |
| 110 | + return err | |
| 111 | +} | |
| 112 | + | |
| 113 | +func (c *loggingConn) Receive() (interface{}, error) { | |
| 114 | + reply, err := c.Conn.Receive() | |
| 115 | + c.print("Receive", "", nil, reply, err) | |
| 116 | + return reply, err | |
| 117 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,442 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "bytes" | |
| 19 | + "container/list" | |
| 20 | + "crypto/rand" | |
| 21 | + "crypto/sha1" | |
| 22 | + "errors" | |
| 23 | + "io" | |
| 24 | + "strconv" | |
| 25 | + "sync" | |
| 26 | + "time" | |
| 27 | + | |
| 28 | + "github.com/garyburd/redigo/internal" | |
| 29 | +) | |
| 30 | + | |
| 31 | +var nowFunc = time.Now // for testing | |
| 32 | + | |
| 33 | +// ErrPoolExhausted is returned from a pool connection method (Do, Send, | |
| 34 | +// Receive, Flush, Err) when the maximum number of database connections in the | |
| 35 | +// pool has been reached. | |
| 36 | +var ErrPoolExhausted = errors.New("redigo: connection pool exhausted") | |
| 37 | + | |
| 38 | +var ( | |
| 39 | + errPoolClosed = errors.New("redigo: connection pool closed") | |
| 40 | + errConnClosed = errors.New("redigo: connection closed") | |
| 41 | +) | |
| 42 | + | |
| 43 | +// Pool maintains a pool of connections. The application calls the Get method | |
| 44 | +// to get a connection from the pool and the connection's Close method to | |
| 45 | +// return the connection's resources to the pool. | |
| 46 | +// | |
| 47 | +// The following example shows how to use a pool in a web application. The | |
| 48 | +// application creates a pool at application startup and makes it available to | |
| 49 | +// request handlers using a package level variable. The pool configuration used | |
| 50 | +// here is an example, not a recommendation. | |
| 51 | +// | |
| 52 | +// func newPool(addr string) *redis.Pool { | |
| 53 | +// return &redis.Pool{ | |
| 54 | +// MaxIdle: 3, | |
| 55 | +// IdleTimeout: 240 * time.Second, | |
| 56 | +// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) }, | |
| 57 | +// } | |
| 58 | +// } | |
| 59 | +// | |
| 60 | +// var ( | |
| 61 | +// pool *redis.Pool | |
| 62 | +// redisServer = flag.String("redisServer", ":6379", "") | |
| 63 | +// ) | |
| 64 | +// | |
| 65 | +// func main() { | |
| 66 | +// flag.Parse() | |
| 67 | +// pool = newPool(*redisServer) | |
| 68 | +// ... | |
| 69 | +// } | |
| 70 | +// | |
| 71 | +// A request handler gets a connection from the pool and closes the connection | |
| 72 | +// when the handler is done: | |
| 73 | +// | |
| 74 | +// func serveHome(w http.ResponseWriter, r *http.Request) { | |
| 75 | +// conn := pool.Get() | |
| 76 | +// defer conn.Close() | |
| 77 | +// ... | |
| 78 | +// } | |
| 79 | +// | |
| 80 | +// Use the Dial function to authenticate connections with the AUTH command or | |
| 81 | +// select a database with the SELECT command: | |
| 82 | +// | |
| 83 | +// pool := &redis.Pool{ | |
| 84 | +// // Other pool configuration not shown in this example. | |
| 85 | +// Dial: func () (redis.Conn, error) { | |
| 86 | +// c, err := redis.Dial("tcp", server) | |
| 87 | +// if err != nil { | |
| 88 | +// return nil, err | |
| 89 | +// } | |
| 90 | +// if _, err := c.Do("AUTH", password); err != nil { | |
| 91 | +// c.Close() | |
| 92 | +// return nil, err | |
| 93 | +// } | |
| 94 | +// if _, err := c.Do("SELECT", db); err != nil { | |
| 95 | +// c.Close() | |
| 96 | +// return nil, err | |
| 97 | +// } | |
| 98 | +// return c, nil | |
| 99 | +// } | |
| 100 | +// } | |
| 101 | +// | |
| 102 | +// Use the TestOnBorrow function to check the health of an idle connection | |
| 103 | +// before the connection is returned to the application. This example PINGs | |
| 104 | +// connections that have been idle more than a minute: | |
| 105 | +// | |
| 106 | +// pool := &redis.Pool{ | |
| 107 | +// // Other pool configuration not shown in this example. | |
| 108 | +// TestOnBorrow: func(c redis.Conn, t time.Time) error { | |
| 109 | +// if time.Since(t) < time.Minute { | |
| 110 | +// return nil | |
| 111 | +// } | |
| 112 | +// _, err := c.Do("PING") | |
| 113 | +// return err | |
| 114 | +// }, | |
| 115 | +// } | |
| 116 | +// | |
| 117 | +type Pool struct { | |
| 118 | + // Dial is an application supplied function for creating and configuring a | |
| 119 | + // connection. | |
| 120 | + // | |
| 121 | + // The connection returned from Dial must not be in a special state | |
| 122 | + // (subscribed to pubsub channel, transaction started, ...). | |
| 123 | + Dial func() (Conn, error) | |
| 124 | + | |
| 125 | + // TestOnBorrow is an optional application supplied function for checking | |
| 126 | + // the health of an idle connection before the connection is used again by | |
| 127 | + // the application. Argument t is the time that the connection was returned | |
| 128 | + // to the pool. If the function returns an error, then the connection is | |
| 129 | + // closed. | |
| 130 | + TestOnBorrow func(c Conn, t time.Time) error | |
| 131 | + | |
| 132 | + // Maximum number of idle connections in the pool. | |
| 133 | + MaxIdle int | |
| 134 | + | |
| 135 | + // Maximum number of connections allocated by the pool at a given time. | |
| 136 | + // When zero, there is no limit on the number of connections in the pool. | |
| 137 | + MaxActive int | |
| 138 | + | |
| 139 | + // Close connections after remaining idle for this duration. If the value | |
| 140 | + // is zero, then idle connections are not closed. Applications should set | |
| 141 | + // the timeout to a value less than the server's timeout. | |
| 142 | + IdleTimeout time.Duration | |
| 143 | + | |
| 144 | + // If Wait is true and the pool is at the MaxActive limit, then Get() waits | |
| 145 | + // for a connection to be returned to the pool before returning. | |
| 146 | + Wait bool | |
| 147 | + | |
| 148 | + // mu protects fields defined below. | |
| 149 | + mu sync.Mutex | |
| 150 | + cond *sync.Cond | |
| 151 | + closed bool | |
| 152 | + active int | |
| 153 | + | |
| 154 | + // Stack of idleConn with most recently used at the front. | |
| 155 | + idle list.List | |
| 156 | +} | |
| 157 | + | |
| 158 | +type idleConn struct { | |
| 159 | + c Conn | |
| 160 | + t time.Time | |
| 161 | +} | |
| 162 | + | |
| 163 | +// NewPool creates a new pool. | |
| 164 | +// | |
| 165 | +// Deprecated: Initialize the Pool directory as shown in the example. | |
| 166 | +func NewPool(newFn func() (Conn, error), maxIdle int) *Pool { | |
| 167 | + return &Pool{Dial: newFn, MaxIdle: maxIdle} | |
| 168 | +} | |
| 169 | + | |
| 170 | +// Get gets a connection. The application must close the returned connection. | |
| 171 | +// This method always returns a valid connection so that applications can defer | |
| 172 | +// error handling to the first use of the connection. If there is an error | |
| 173 | +// getting an underlying connection, then the connection Err, Do, Send, Flush | |
| 174 | +// and Receive methods return that error. | |
| 175 | +func (p *Pool) Get() Conn { | |
| 176 | + c, err := p.get() | |
| 177 | + if err != nil { | |
| 178 | + return errorConnection{err} | |
| 179 | + } | |
| 180 | + return &pooledConnection{p: p, c: c} | |
| 181 | +} | |
| 182 | + | |
| 183 | +// PoolStats contains pool statistics. | |
| 184 | +type PoolStats struct { | |
| 185 | + // ActiveCount is the number of connections in the pool. The count includes idle connections and connections in use. | |
| 186 | + ActiveCount int | |
| 187 | + // IdleCount is the number of idle connections in the pool. | |
| 188 | + IdleCount int | |
| 189 | +} | |
| 190 | + | |
| 191 | +// Stats returns pool's statistics. | |
| 192 | +func (p *Pool) Stats() PoolStats { | |
| 193 | + p.mu.Lock() | |
| 194 | + stats := PoolStats{ | |
| 195 | + ActiveCount: p.active, | |
| 196 | + IdleCount: p.idle.Len(), | |
| 197 | + } | |
| 198 | + p.mu.Unlock() | |
| 199 | + | |
| 200 | + return stats | |
| 201 | +} | |
| 202 | + | |
| 203 | +// ActiveCount returns the number of connections in the pool. The count includes idle connections and connections in use. | |
| 204 | +func (p *Pool) ActiveCount() int { | |
| 205 | + p.mu.Lock() | |
| 206 | + active := p.active | |
| 207 | + p.mu.Unlock() | |
| 208 | + return active | |
| 209 | +} | |
| 210 | + | |
| 211 | +// IdleCount returns the number of idle connections in the pool. | |
| 212 | +func (p *Pool) IdleCount() int { | |
| 213 | + p.mu.Lock() | |
| 214 | + idle := p.idle.Len() | |
| 215 | + p.mu.Unlock() | |
| 216 | + return idle | |
| 217 | +} | |
| 218 | + | |
| 219 | +// Close releases the resources used by the pool. | |
| 220 | +func (p *Pool) Close() error { | |
| 221 | + p.mu.Lock() | |
| 222 | + idle := p.idle | |
| 223 | + p.idle.Init() | |
| 224 | + p.closed = true | |
| 225 | + p.active -= idle.Len() | |
| 226 | + if p.cond != nil { | |
| 227 | + p.cond.Broadcast() | |
| 228 | + } | |
| 229 | + p.mu.Unlock() | |
| 230 | + for e := idle.Front(); e != nil; e = e.Next() { | |
| 231 | + e.Value.(idleConn).c.Close() | |
| 232 | + } | |
| 233 | + return nil | |
| 234 | +} | |
| 235 | + | |
| 236 | +// release decrements the active count and signals waiters. The caller must | |
| 237 | +// hold p.mu during the call. | |
| 238 | +func (p *Pool) release() { | |
| 239 | + p.active -= 1 | |
| 240 | + if p.cond != nil { | |
| 241 | + p.cond.Signal() | |
| 242 | + } | |
| 243 | +} | |
| 244 | + | |
| 245 | +// get prunes stale connections and returns a connection from the idle list or | |
| 246 | +// creates a new connection. | |
| 247 | +func (p *Pool) get() (Conn, error) { | |
| 248 | + p.mu.Lock() | |
| 249 | + | |
| 250 | + // Prune stale connections. | |
| 251 | + | |
| 252 | + if timeout := p.IdleTimeout; timeout > 0 { | |
| 253 | + for i, n := 0, p.idle.Len(); i < n; i++ { | |
| 254 | + e := p.idle.Back() | |
| 255 | + if e == nil { | |
| 256 | + break | |
| 257 | + } | |
| 258 | + ic := e.Value.(idleConn) | |
| 259 | + if ic.t.Add(timeout).After(nowFunc()) { | |
| 260 | + break | |
| 261 | + } | |
| 262 | + p.idle.Remove(e) | |
| 263 | + p.release() | |
| 264 | + p.mu.Unlock() | |
| 265 | + ic.c.Close() | |
| 266 | + p.mu.Lock() | |
| 267 | + } | |
| 268 | + } | |
| 269 | + | |
| 270 | + for { | |
| 271 | + // Get idle connection. | |
| 272 | + | |
| 273 | + for i, n := 0, p.idle.Len(); i < n; i++ { | |
| 274 | + e := p.idle.Front() | |
| 275 | + if e == nil { | |
| 276 | + break | |
| 277 | + } | |
| 278 | + ic := e.Value.(idleConn) | |
| 279 | + p.idle.Remove(e) | |
| 280 | + test := p.TestOnBorrow | |
| 281 | + p.mu.Unlock() | |
| 282 | + if test == nil || test(ic.c, ic.t) == nil { | |
| 283 | + return ic.c, nil | |
| 284 | + } | |
| 285 | + ic.c.Close() | |
| 286 | + p.mu.Lock() | |
| 287 | + p.release() | |
| 288 | + } | |
| 289 | + | |
| 290 | + // Check for pool closed before dialing a new connection. | |
| 291 | + | |
| 292 | + if p.closed { | |
| 293 | + p.mu.Unlock() | |
| 294 | + return nil, errors.New("redigo: get on closed pool") | |
| 295 | + } | |
| 296 | + | |
| 297 | + // Dial new connection if under limit. | |
| 298 | + | |
| 299 | + if p.MaxActive == 0 || p.active < p.MaxActive { | |
| 300 | + dial := p.Dial | |
| 301 | + p.active += 1 | |
| 302 | + p.mu.Unlock() | |
| 303 | + c, err := dial() | |
| 304 | + if err != nil { | |
| 305 | + p.mu.Lock() | |
| 306 | + p.release() | |
| 307 | + p.mu.Unlock() | |
| 308 | + c = nil | |
| 309 | + } | |
| 310 | + return c, err | |
| 311 | + } | |
| 312 | + | |
| 313 | + if !p.Wait { | |
| 314 | + p.mu.Unlock() | |
| 315 | + return nil, ErrPoolExhausted | |
| 316 | + } | |
| 317 | + | |
| 318 | + if p.cond == nil { | |
| 319 | + p.cond = sync.NewCond(&p.mu) | |
| 320 | + } | |
| 321 | + p.cond.Wait() | |
| 322 | + } | |
| 323 | +} | |
| 324 | + | |
| 325 | +func (p *Pool) put(c Conn, forceClose bool) error { | |
| 326 | + err := c.Err() | |
| 327 | + p.mu.Lock() | |
| 328 | + if !p.closed && err == nil && !forceClose { | |
| 329 | + p.idle.PushFront(idleConn{t: nowFunc(), c: c}) | |
| 330 | + if p.idle.Len() > p.MaxIdle { | |
| 331 | + c = p.idle.Remove(p.idle.Back()).(idleConn).c | |
| 332 | + } else { | |
| 333 | + c = nil | |
| 334 | + } | |
| 335 | + } | |
| 336 | + | |
| 337 | + if c == nil { | |
| 338 | + if p.cond != nil { | |
| 339 | + p.cond.Signal() | |
| 340 | + } | |
| 341 | + p.mu.Unlock() | |
| 342 | + return nil | |
| 343 | + } | |
| 344 | + | |
| 345 | + p.release() | |
| 346 | + p.mu.Unlock() | |
| 347 | + return c.Close() | |
| 348 | +} | |
| 349 | + | |
| 350 | +type pooledConnection struct { | |
| 351 | + p *Pool | |
| 352 | + c Conn | |
| 353 | + state int | |
| 354 | +} | |
| 355 | + | |
| 356 | +var ( | |
| 357 | + sentinel []byte | |
| 358 | + sentinelOnce sync.Once | |
| 359 | +) | |
| 360 | + | |
| 361 | +func initSentinel() { | |
| 362 | + p := make([]byte, 64) | |
| 363 | + if _, err := rand.Read(p); err == nil { | |
| 364 | + sentinel = p | |
| 365 | + } else { | |
| 366 | + h := sha1.New() | |
| 367 | + io.WriteString(h, "Oops, rand failed. Use time instead.") | |
| 368 | + io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10)) | |
| 369 | + sentinel = h.Sum(nil) | |
| 370 | + } | |
| 371 | +} | |
| 372 | + | |
| 373 | +func (pc *pooledConnection) Close() error { | |
| 374 | + c := pc.c | |
| 375 | + if _, ok := c.(errorConnection); ok { | |
| 376 | + return nil | |
| 377 | + } | |
| 378 | + pc.c = errorConnection{errConnClosed} | |
| 379 | + | |
| 380 | + if pc.state&internal.MultiState != 0 { | |
| 381 | + c.Send("DISCARD") | |
| 382 | + pc.state &^= (internal.MultiState | internal.WatchState) | |
| 383 | + } else if pc.state&internal.WatchState != 0 { | |
| 384 | + c.Send("UNWATCH") | |
| 385 | + pc.state &^= internal.WatchState | |
| 386 | + } | |
| 387 | + if pc.state&internal.SubscribeState != 0 { | |
| 388 | + c.Send("UNSUBSCRIBE") | |
| 389 | + c.Send("PUNSUBSCRIBE") | |
| 390 | + // To detect the end of the message stream, ask the server to echo | |
| 391 | + // a sentinel value and read until we see that value. | |
| 392 | + sentinelOnce.Do(initSentinel) | |
| 393 | + c.Send("ECHO", sentinel) | |
| 394 | + c.Flush() | |
| 395 | + for { | |
| 396 | + p, err := c.Receive() | |
| 397 | + if err != nil { | |
| 398 | + break | |
| 399 | + } | |
| 400 | + if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) { | |
| 401 | + pc.state &^= internal.SubscribeState | |
| 402 | + break | |
| 403 | + } | |
| 404 | + } | |
| 405 | + } | |
| 406 | + c.Do("") | |
| 407 | + pc.p.put(c, pc.state != 0) | |
| 408 | + return nil | |
| 409 | +} | |
| 410 | + | |
| 411 | +func (pc *pooledConnection) Err() error { | |
| 412 | + return pc.c.Err() | |
| 413 | +} | |
| 414 | + | |
| 415 | +func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) { | |
| 416 | + ci := internal.LookupCommandInfo(commandName) | |
| 417 | + pc.state = (pc.state | ci.Set) &^ ci.Clear | |
| 418 | + return pc.c.Do(commandName, args...) | |
| 419 | +} | |
| 420 | + | |
| 421 | +func (pc *pooledConnection) Send(commandName string, args ...interface{}) error { | |
| 422 | + ci := internal.LookupCommandInfo(commandName) | |
| 423 | + pc.state = (pc.state | ci.Set) &^ ci.Clear | |
| 424 | + return pc.c.Send(commandName, args...) | |
| 425 | +} | |
| 426 | + | |
| 427 | +func (pc *pooledConnection) Flush() error { | |
| 428 | + return pc.c.Flush() | |
| 429 | +} | |
| 430 | + | |
| 431 | +func (pc *pooledConnection) Receive() (reply interface{}, err error) { | |
| 432 | + return pc.c.Receive() | |
| 433 | +} | |
| 434 | + | |
| 435 | +type errorConnection struct{ err error } | |
| 436 | + | |
| 437 | +func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err } | |
| 438 | +func (ec errorConnection) Send(string, ...interface{}) error { return ec.err } | |
| 439 | +func (ec errorConnection) Err() error { return ec.err } | |
| 440 | +func (ec errorConnection) Close() error { return ec.err } | |
| 441 | +func (ec errorConnection) Flush() error { return ec.err } | |
| 442 | +func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err } | ... | ... |
| ... | ... | @@ -0,0 +1,691 @@ |
| 1 | +// Copyright 2011 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "errors" | |
| 19 | + "io" | |
| 20 | + "reflect" | |
| 21 | + "sync" | |
| 22 | + "testing" | |
| 23 | + "time" | |
| 24 | + | |
| 25 | + "github.com/garyburd/redigo/redis" | |
| 26 | +) | |
| 27 | + | |
| 28 | +type poolTestConn struct { | |
| 29 | + d *poolDialer | |
| 30 | + err error | |
| 31 | + redis.Conn | |
| 32 | +} | |
| 33 | + | |
| 34 | +func (c *poolTestConn) Close() error { | |
| 35 | + c.d.mu.Lock() | |
| 36 | + c.d.open -= 1 | |
| 37 | + c.d.mu.Unlock() | |
| 38 | + return c.Conn.Close() | |
| 39 | +} | |
| 40 | + | |
| 41 | +func (c *poolTestConn) Err() error { return c.err } | |
| 42 | + | |
| 43 | +func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) { | |
| 44 | + if commandName == "ERR" { | |
| 45 | + c.err = args[0].(error) | |
| 46 | + commandName = "PING" | |
| 47 | + } | |
| 48 | + if commandName != "" { | |
| 49 | + c.d.commands = append(c.d.commands, commandName) | |
| 50 | + } | |
| 51 | + return c.Conn.Do(commandName, args...) | |
| 52 | +} | |
| 53 | + | |
| 54 | +func (c *poolTestConn) Send(commandName string, args ...interface{}) error { | |
| 55 | + c.d.commands = append(c.d.commands, commandName) | |
| 56 | + return c.Conn.Send(commandName, args...) | |
| 57 | +} | |
| 58 | + | |
| 59 | +type poolDialer struct { | |
| 60 | + mu sync.Mutex | |
| 61 | + t *testing.T | |
| 62 | + dialed int | |
| 63 | + open int | |
| 64 | + commands []string | |
| 65 | + dialErr error | |
| 66 | +} | |
| 67 | + | |
| 68 | +func (d *poolDialer) dial() (redis.Conn, error) { | |
| 69 | + d.mu.Lock() | |
| 70 | + d.dialed += 1 | |
| 71 | + dialErr := d.dialErr | |
| 72 | + d.mu.Unlock() | |
| 73 | + if dialErr != nil { | |
| 74 | + return nil, d.dialErr | |
| 75 | + } | |
| 76 | + c, err := redis.DialDefaultServer() | |
| 77 | + if err != nil { | |
| 78 | + return nil, err | |
| 79 | + } | |
| 80 | + d.mu.Lock() | |
| 81 | + d.open += 1 | |
| 82 | + d.mu.Unlock() | |
| 83 | + return &poolTestConn{d: d, Conn: c}, nil | |
| 84 | +} | |
| 85 | + | |
| 86 | +func (d *poolDialer) check(message string, p *redis.Pool, dialed, open, inuse int) { | |
| 87 | + d.mu.Lock() | |
| 88 | + if d.dialed != dialed { | |
| 89 | + d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed) | |
| 90 | + } | |
| 91 | + if d.open != open { | |
| 92 | + d.t.Errorf("%s: open=%d, want %d", message, d.open, open) | |
| 93 | + } | |
| 94 | + | |
| 95 | + stats := p.Stats() | |
| 96 | + | |
| 97 | + if stats.ActiveCount != open { | |
| 98 | + d.t.Errorf("%s: active=%d, want %d", message, stats.ActiveCount, open) | |
| 99 | + } | |
| 100 | + if stats.IdleCount != open-inuse { | |
| 101 | + d.t.Errorf("%s: idle=%d, want %d", message, stats.IdleCount, open-inuse) | |
| 102 | + } | |
| 103 | + | |
| 104 | + d.mu.Unlock() | |
| 105 | +} | |
| 106 | + | |
| 107 | +func TestPoolReuse(t *testing.T) { | |
| 108 | + d := poolDialer{t: t} | |
| 109 | + p := &redis.Pool{ | |
| 110 | + MaxIdle: 2, | |
| 111 | + Dial: d.dial, | |
| 112 | + } | |
| 113 | + | |
| 114 | + for i := 0; i < 10; i++ { | |
| 115 | + c1 := p.Get() | |
| 116 | + c1.Do("PING") | |
| 117 | + c2 := p.Get() | |
| 118 | + c2.Do("PING") | |
| 119 | + c1.Close() | |
| 120 | + c2.Close() | |
| 121 | + } | |
| 122 | + | |
| 123 | + d.check("before close", p, 2, 2, 0) | |
| 124 | + p.Close() | |
| 125 | + d.check("after close", p, 2, 0, 0) | |
| 126 | +} | |
| 127 | + | |
| 128 | +func TestPoolMaxIdle(t *testing.T) { | |
| 129 | + d := poolDialer{t: t} | |
| 130 | + p := &redis.Pool{ | |
| 131 | + MaxIdle: 2, | |
| 132 | + Dial: d.dial, | |
| 133 | + } | |
| 134 | + defer p.Close() | |
| 135 | + | |
| 136 | + for i := 0; i < 10; i++ { | |
| 137 | + c1 := p.Get() | |
| 138 | + c1.Do("PING") | |
| 139 | + c2 := p.Get() | |
| 140 | + c2.Do("PING") | |
| 141 | + c3 := p.Get() | |
| 142 | + c3.Do("PING") | |
| 143 | + c1.Close() | |
| 144 | + c2.Close() | |
| 145 | + c3.Close() | |
| 146 | + } | |
| 147 | + d.check("before close", p, 12, 2, 0) | |
| 148 | + p.Close() | |
| 149 | + d.check("after close", p, 12, 0, 0) | |
| 150 | +} | |
| 151 | + | |
| 152 | +func TestPoolError(t *testing.T) { | |
| 153 | + d := poolDialer{t: t} | |
| 154 | + p := &redis.Pool{ | |
| 155 | + MaxIdle: 2, | |
| 156 | + Dial: d.dial, | |
| 157 | + } | |
| 158 | + defer p.Close() | |
| 159 | + | |
| 160 | + c := p.Get() | |
| 161 | + c.Do("ERR", io.EOF) | |
| 162 | + if c.Err() == nil { | |
| 163 | + t.Errorf("expected c.Err() != nil") | |
| 164 | + } | |
| 165 | + c.Close() | |
| 166 | + | |
| 167 | + c = p.Get() | |
| 168 | + c.Do("ERR", io.EOF) | |
| 169 | + c.Close() | |
| 170 | + | |
| 171 | + d.check(".", p, 2, 0, 0) | |
| 172 | +} | |
| 173 | + | |
| 174 | +func TestPoolClose(t *testing.T) { | |
| 175 | + d := poolDialer{t: t} | |
| 176 | + p := &redis.Pool{ | |
| 177 | + MaxIdle: 2, | |
| 178 | + Dial: d.dial, | |
| 179 | + } | |
| 180 | + defer p.Close() | |
| 181 | + | |
| 182 | + c1 := p.Get() | |
| 183 | + c1.Do("PING") | |
| 184 | + c2 := p.Get() | |
| 185 | + c2.Do("PING") | |
| 186 | + c3 := p.Get() | |
| 187 | + c3.Do("PING") | |
| 188 | + | |
| 189 | + c1.Close() | |
| 190 | + if _, err := c1.Do("PING"); err == nil { | |
| 191 | + t.Errorf("expected error after connection closed") | |
| 192 | + } | |
| 193 | + | |
| 194 | + c2.Close() | |
| 195 | + c2.Close() | |
| 196 | + | |
| 197 | + p.Close() | |
| 198 | + | |
| 199 | + d.check("after pool close", p, 3, 1, 1) | |
| 200 | + | |
| 201 | + if _, err := c1.Do("PING"); err == nil { | |
| 202 | + t.Errorf("expected error after connection and pool closed") | |
| 203 | + } | |
| 204 | + | |
| 205 | + c3.Close() | |
| 206 | + | |
| 207 | + d.check("after conn close", p, 3, 0, 0) | |
| 208 | + | |
| 209 | + c1 = p.Get() | |
| 210 | + if _, err := c1.Do("PING"); err == nil { | |
| 211 | + t.Errorf("expected error after pool closed") | |
| 212 | + } | |
| 213 | +} | |
| 214 | + | |
| 215 | +func TestPoolTimeout(t *testing.T) { | |
| 216 | + d := poolDialer{t: t} | |
| 217 | + p := &redis.Pool{ | |
| 218 | + MaxIdle: 2, | |
| 219 | + IdleTimeout: 300 * time.Second, | |
| 220 | + Dial: d.dial, | |
| 221 | + } | |
| 222 | + defer p.Close() | |
| 223 | + | |
| 224 | + now := time.Now() | |
| 225 | + redis.SetNowFunc(func() time.Time { return now }) | |
| 226 | + defer redis.SetNowFunc(time.Now) | |
| 227 | + | |
| 228 | + c := p.Get() | |
| 229 | + c.Do("PING") | |
| 230 | + c.Close() | |
| 231 | + | |
| 232 | + d.check("1", p, 1, 1, 0) | |
| 233 | + | |
| 234 | + now = now.Add(p.IdleTimeout) | |
| 235 | + | |
| 236 | + c = p.Get() | |
| 237 | + c.Do("PING") | |
| 238 | + c.Close() | |
| 239 | + | |
| 240 | + d.check("2", p, 2, 1, 0) | |
| 241 | +} | |
| 242 | + | |
| 243 | +func TestPoolConcurrenSendReceive(t *testing.T) { | |
| 244 | + p := &redis.Pool{ | |
| 245 | + Dial: redis.DialDefaultServer, | |
| 246 | + } | |
| 247 | + defer p.Close() | |
| 248 | + | |
| 249 | + c := p.Get() | |
| 250 | + done := make(chan error, 1) | |
| 251 | + go func() { | |
| 252 | + _, err := c.Receive() | |
| 253 | + done <- err | |
| 254 | + }() | |
| 255 | + c.Send("PING") | |
| 256 | + c.Flush() | |
| 257 | + err := <-done | |
| 258 | + if err != nil { | |
| 259 | + t.Fatalf("Receive() returned error %v", err) | |
| 260 | + } | |
| 261 | + _, err = c.Do("") | |
| 262 | + if err != nil { | |
| 263 | + t.Fatalf("Do() returned error %v", err) | |
| 264 | + } | |
| 265 | + c.Close() | |
| 266 | +} | |
| 267 | + | |
| 268 | +func TestPoolBorrowCheck(t *testing.T) { | |
| 269 | + d := poolDialer{t: t} | |
| 270 | + p := &redis.Pool{ | |
| 271 | + MaxIdle: 2, | |
| 272 | + Dial: d.dial, | |
| 273 | + TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") }, | |
| 274 | + } | |
| 275 | + defer p.Close() | |
| 276 | + | |
| 277 | + for i := 0; i < 10; i++ { | |
| 278 | + c := p.Get() | |
| 279 | + c.Do("PING") | |
| 280 | + c.Close() | |
| 281 | + } | |
| 282 | + d.check("1", p, 10, 1, 0) | |
| 283 | +} | |
| 284 | + | |
| 285 | +func TestPoolMaxActive(t *testing.T) { | |
| 286 | + d := poolDialer{t: t} | |
| 287 | + p := &redis.Pool{ | |
| 288 | + MaxIdle: 2, | |
| 289 | + MaxActive: 2, | |
| 290 | + Dial: d.dial, | |
| 291 | + } | |
| 292 | + defer p.Close() | |
| 293 | + | |
| 294 | + c1 := p.Get() | |
| 295 | + c1.Do("PING") | |
| 296 | + c2 := p.Get() | |
| 297 | + c2.Do("PING") | |
| 298 | + | |
| 299 | + d.check("1", p, 2, 2, 2) | |
| 300 | + | |
| 301 | + c3 := p.Get() | |
| 302 | + if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted { | |
| 303 | + t.Errorf("expected pool exhausted") | |
| 304 | + } | |
| 305 | + | |
| 306 | + c3.Close() | |
| 307 | + d.check("2", p, 2, 2, 2) | |
| 308 | + c2.Close() | |
| 309 | + d.check("3", p, 2, 2, 1) | |
| 310 | + | |
| 311 | + c3 = p.Get() | |
| 312 | + if _, err := c3.Do("PING"); err != nil { | |
| 313 | + t.Errorf("expected good channel, err=%v", err) | |
| 314 | + } | |
| 315 | + c3.Close() | |
| 316 | + | |
| 317 | + d.check("4", p, 2, 2, 1) | |
| 318 | +} | |
| 319 | + | |
| 320 | +func TestPoolMonitorCleanup(t *testing.T) { | |
| 321 | + d := poolDialer{t: t} | |
| 322 | + p := &redis.Pool{ | |
| 323 | + MaxIdle: 2, | |
| 324 | + MaxActive: 2, | |
| 325 | + Dial: d.dial, | |
| 326 | + } | |
| 327 | + defer p.Close() | |
| 328 | + | |
| 329 | + c := p.Get() | |
| 330 | + c.Send("MONITOR") | |
| 331 | + c.Close() | |
| 332 | + | |
| 333 | + d.check("", p, 1, 0, 0) | |
| 334 | +} | |
| 335 | + | |
| 336 | +func TestPoolPubSubCleanup(t *testing.T) { | |
| 337 | + d := poolDialer{t: t} | |
| 338 | + p := &redis.Pool{ | |
| 339 | + MaxIdle: 2, | |
| 340 | + MaxActive: 2, | |
| 341 | + Dial: d.dial, | |
| 342 | + } | |
| 343 | + defer p.Close() | |
| 344 | + | |
| 345 | + c := p.Get() | |
| 346 | + c.Send("SUBSCRIBE", "x") | |
| 347 | + c.Close() | |
| 348 | + | |
| 349 | + want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} | |
| 350 | + if !reflect.DeepEqual(d.commands, want) { | |
| 351 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 352 | + } | |
| 353 | + d.commands = nil | |
| 354 | + | |
| 355 | + c = p.Get() | |
| 356 | + c.Send("PSUBSCRIBE", "x*") | |
| 357 | + c.Close() | |
| 358 | + | |
| 359 | + want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"} | |
| 360 | + if !reflect.DeepEqual(d.commands, want) { | |
| 361 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 362 | + } | |
| 363 | + d.commands = nil | |
| 364 | +} | |
| 365 | + | |
| 366 | +func TestPoolTransactionCleanup(t *testing.T) { | |
| 367 | + d := poolDialer{t: t} | |
| 368 | + p := &redis.Pool{ | |
| 369 | + MaxIdle: 2, | |
| 370 | + MaxActive: 2, | |
| 371 | + Dial: d.dial, | |
| 372 | + } | |
| 373 | + defer p.Close() | |
| 374 | + | |
| 375 | + c := p.Get() | |
| 376 | + c.Do("WATCH", "key") | |
| 377 | + c.Do("PING") | |
| 378 | + c.Close() | |
| 379 | + | |
| 380 | + want := []string{"WATCH", "PING", "UNWATCH"} | |
| 381 | + if !reflect.DeepEqual(d.commands, want) { | |
| 382 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 383 | + } | |
| 384 | + d.commands = nil | |
| 385 | + | |
| 386 | + c = p.Get() | |
| 387 | + c.Do("WATCH", "key") | |
| 388 | + c.Do("UNWATCH") | |
| 389 | + c.Do("PING") | |
| 390 | + c.Close() | |
| 391 | + | |
| 392 | + want = []string{"WATCH", "UNWATCH", "PING"} | |
| 393 | + if !reflect.DeepEqual(d.commands, want) { | |
| 394 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 395 | + } | |
| 396 | + d.commands = nil | |
| 397 | + | |
| 398 | + c = p.Get() | |
| 399 | + c.Do("WATCH", "key") | |
| 400 | + c.Do("MULTI") | |
| 401 | + c.Do("PING") | |
| 402 | + c.Close() | |
| 403 | + | |
| 404 | + want = []string{"WATCH", "MULTI", "PING", "DISCARD"} | |
| 405 | + if !reflect.DeepEqual(d.commands, want) { | |
| 406 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 407 | + } | |
| 408 | + d.commands = nil | |
| 409 | + | |
| 410 | + c = p.Get() | |
| 411 | + c.Do("WATCH", "key") | |
| 412 | + c.Do("MULTI") | |
| 413 | + c.Do("DISCARD") | |
| 414 | + c.Do("PING") | |
| 415 | + c.Close() | |
| 416 | + | |
| 417 | + want = []string{"WATCH", "MULTI", "DISCARD", "PING"} | |
| 418 | + if !reflect.DeepEqual(d.commands, want) { | |
| 419 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 420 | + } | |
| 421 | + d.commands = nil | |
| 422 | + | |
| 423 | + c = p.Get() | |
| 424 | + c.Do("WATCH", "key") | |
| 425 | + c.Do("MULTI") | |
| 426 | + c.Do("EXEC") | |
| 427 | + c.Do("PING") | |
| 428 | + c.Close() | |
| 429 | + | |
| 430 | + want = []string{"WATCH", "MULTI", "EXEC", "PING"} | |
| 431 | + if !reflect.DeepEqual(d.commands, want) { | |
| 432 | + t.Errorf("got commands %v, want %v", d.commands, want) | |
| 433 | + } | |
| 434 | + d.commands = nil | |
| 435 | +} | |
| 436 | + | |
| 437 | +func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error { | |
| 438 | + errs := make(chan error, 10) | |
| 439 | + for i := 0; i < cap(errs); i++ { | |
| 440 | + go func() { | |
| 441 | + c := p.Get() | |
| 442 | + _, err := c.Do(cmd, args...) | |
| 443 | + c.Close() | |
| 444 | + errs <- err | |
| 445 | + }() | |
| 446 | + } | |
| 447 | + | |
| 448 | + // Wait for goroutines to block. | |
| 449 | + time.Sleep(time.Second / 4) | |
| 450 | + | |
| 451 | + return errs | |
| 452 | +} | |
| 453 | + | |
| 454 | +func TestWaitPool(t *testing.T) { | |
| 455 | + d := poolDialer{t: t} | |
| 456 | + p := &redis.Pool{ | |
| 457 | + MaxIdle: 1, | |
| 458 | + MaxActive: 1, | |
| 459 | + Dial: d.dial, | |
| 460 | + Wait: true, | |
| 461 | + } | |
| 462 | + defer p.Close() | |
| 463 | + | |
| 464 | + c := p.Get() | |
| 465 | + errs := startGoroutines(p, "PING") | |
| 466 | + d.check("before close", p, 1, 1, 1) | |
| 467 | + c.Close() | |
| 468 | + timeout := time.After(2 * time.Second) | |
| 469 | + for i := 0; i < cap(errs); i++ { | |
| 470 | + select { | |
| 471 | + case err := <-errs: | |
| 472 | + if err != nil { | |
| 473 | + t.Fatal(err) | |
| 474 | + } | |
| 475 | + case <-timeout: | |
| 476 | + t.Fatalf("timeout waiting for blocked goroutine %d", i) | |
| 477 | + } | |
| 478 | + } | |
| 479 | + d.check("done", p, 1, 1, 0) | |
| 480 | +} | |
| 481 | + | |
| 482 | +func TestWaitPoolClose(t *testing.T) { | |
| 483 | + d := poolDialer{t: t} | |
| 484 | + p := &redis.Pool{ | |
| 485 | + MaxIdle: 1, | |
| 486 | + MaxActive: 1, | |
| 487 | + Dial: d.dial, | |
| 488 | + Wait: true, | |
| 489 | + } | |
| 490 | + defer p.Close() | |
| 491 | + | |
| 492 | + c := p.Get() | |
| 493 | + if _, err := c.Do("PING"); err != nil { | |
| 494 | + t.Fatal(err) | |
| 495 | + } | |
| 496 | + errs := startGoroutines(p, "PING") | |
| 497 | + d.check("before close", p, 1, 1, 1) | |
| 498 | + p.Close() | |
| 499 | + timeout := time.After(2 * time.Second) | |
| 500 | + for i := 0; i < cap(errs); i++ { | |
| 501 | + select { | |
| 502 | + case err := <-errs: | |
| 503 | + switch err { | |
| 504 | + case nil: | |
| 505 | + t.Fatal("blocked goroutine did not get error") | |
| 506 | + case redis.ErrPoolExhausted: | |
| 507 | + t.Fatal("blocked goroutine got pool exhausted error") | |
| 508 | + } | |
| 509 | + case <-timeout: | |
| 510 | + t.Fatal("timeout waiting for blocked goroutine") | |
| 511 | + } | |
| 512 | + } | |
| 513 | + c.Close() | |
| 514 | + d.check("done", p, 1, 0, 0) | |
| 515 | +} | |
| 516 | + | |
| 517 | +func TestWaitPoolCommandError(t *testing.T) { | |
| 518 | + testErr := errors.New("test") | |
| 519 | + d := poolDialer{t: t} | |
| 520 | + p := &redis.Pool{ | |
| 521 | + MaxIdle: 1, | |
| 522 | + MaxActive: 1, | |
| 523 | + Dial: d.dial, | |
| 524 | + Wait: true, | |
| 525 | + } | |
| 526 | + defer p.Close() | |
| 527 | + | |
| 528 | + c := p.Get() | |
| 529 | + errs := startGoroutines(p, "ERR", testErr) | |
| 530 | + d.check("before close", p, 1, 1, 1) | |
| 531 | + c.Close() | |
| 532 | + timeout := time.After(2 * time.Second) | |
| 533 | + for i := 0; i < cap(errs); i++ { | |
| 534 | + select { | |
| 535 | + case err := <-errs: | |
| 536 | + if err != nil { | |
| 537 | + t.Fatal(err) | |
| 538 | + } | |
| 539 | + case <-timeout: | |
| 540 | + t.Fatalf("timeout waiting for blocked goroutine %d", i) | |
| 541 | + } | |
| 542 | + } | |
| 543 | + d.check("done", p, cap(errs), 0, 0) | |
| 544 | +} | |
| 545 | + | |
| 546 | +func TestWaitPoolDialError(t *testing.T) { | |
| 547 | + testErr := errors.New("test") | |
| 548 | + d := poolDialer{t: t} | |
| 549 | + p := &redis.Pool{ | |
| 550 | + MaxIdle: 1, | |
| 551 | + MaxActive: 1, | |
| 552 | + Dial: d.dial, | |
| 553 | + Wait: true, | |
| 554 | + } | |
| 555 | + defer p.Close() | |
| 556 | + | |
| 557 | + c := p.Get() | |
| 558 | + errs := startGoroutines(p, "ERR", testErr) | |
| 559 | + d.check("before close", p, 1, 1, 1) | |
| 560 | + | |
| 561 | + d.dialErr = errors.New("dial") | |
| 562 | + c.Close() | |
| 563 | + | |
| 564 | + nilCount := 0 | |
| 565 | + errCount := 0 | |
| 566 | + timeout := time.After(2 * time.Second) | |
| 567 | + for i := 0; i < cap(errs); i++ { | |
| 568 | + select { | |
| 569 | + case err := <-errs: | |
| 570 | + switch err { | |
| 571 | + case nil: | |
| 572 | + nilCount++ | |
| 573 | + case d.dialErr: | |
| 574 | + errCount++ | |
| 575 | + default: | |
| 576 | + t.Fatalf("expected dial error or nil, got %v", err) | |
| 577 | + } | |
| 578 | + case <-timeout: | |
| 579 | + t.Fatalf("timeout waiting for blocked goroutine %d", i) | |
| 580 | + } | |
| 581 | + } | |
| 582 | + if nilCount != 1 { | |
| 583 | + t.Errorf("expected one nil error, got %d", nilCount) | |
| 584 | + } | |
| 585 | + if errCount != cap(errs)-1 { | |
| 586 | + t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount) | |
| 587 | + } | |
| 588 | + d.check("done", p, cap(errs), 0, 0) | |
| 589 | +} | |
| 590 | + | |
| 591 | +// Borrowing requires us to iterate over the idle connections, unlock the pool, | |
| 592 | +// and perform a blocking operation to check the connection still works. If | |
| 593 | +// TestOnBorrow fails, we must reacquire the lock and continue iteration. This | |
| 594 | +// test ensures that iteration will work correctly if multiple threads are | |
| 595 | +// iterating simultaneously. | |
| 596 | +func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) { | |
| 597 | + const count = 100 | |
| 598 | + | |
| 599 | + // First we'll Create a pool where the pilfering of idle connections fails. | |
| 600 | + d := poolDialer{t: t} | |
| 601 | + p := &redis.Pool{ | |
| 602 | + MaxIdle: count, | |
| 603 | + MaxActive: count, | |
| 604 | + Dial: d.dial, | |
| 605 | + TestOnBorrow: func(c redis.Conn, t time.Time) error { | |
| 606 | + return errors.New("No way back into the real world.") | |
| 607 | + }, | |
| 608 | + } | |
| 609 | + defer p.Close() | |
| 610 | + | |
| 611 | + // Fill the pool with idle connections. | |
| 612 | + conns := make([]redis.Conn, count) | |
| 613 | + for i := range conns { | |
| 614 | + conns[i] = p.Get() | |
| 615 | + } | |
| 616 | + for i := range conns { | |
| 617 | + conns[i].Close() | |
| 618 | + } | |
| 619 | + | |
| 620 | + // Spawn a bunch of goroutines to thrash the pool. | |
| 621 | + var wg sync.WaitGroup | |
| 622 | + wg.Add(count) | |
| 623 | + for i := 0; i < count; i++ { | |
| 624 | + go func() { | |
| 625 | + c := p.Get() | |
| 626 | + if c.Err() != nil { | |
| 627 | + t.Errorf("pool get failed: %v", c.Err()) | |
| 628 | + } | |
| 629 | + c.Close() | |
| 630 | + wg.Done() | |
| 631 | + }() | |
| 632 | + } | |
| 633 | + wg.Wait() | |
| 634 | + if d.dialed != count*2 { | |
| 635 | + t.Errorf("Expected %d dials, got %d", count*2, d.dialed) | |
| 636 | + } | |
| 637 | +} | |
| 638 | + | |
| 639 | +func BenchmarkPoolGet(b *testing.B) { | |
| 640 | + b.StopTimer() | |
| 641 | + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} | |
| 642 | + c := p.Get() | |
| 643 | + if err := c.Err(); err != nil { | |
| 644 | + b.Fatal(err) | |
| 645 | + } | |
| 646 | + c.Close() | |
| 647 | + defer p.Close() | |
| 648 | + b.StartTimer() | |
| 649 | + for i := 0; i < b.N; i++ { | |
| 650 | + c = p.Get() | |
| 651 | + c.Close() | |
| 652 | + } | |
| 653 | +} | |
| 654 | + | |
| 655 | +func BenchmarkPoolGetErr(b *testing.B) { | |
| 656 | + b.StopTimer() | |
| 657 | + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} | |
| 658 | + c := p.Get() | |
| 659 | + if err := c.Err(); err != nil { | |
| 660 | + b.Fatal(err) | |
| 661 | + } | |
| 662 | + c.Close() | |
| 663 | + defer p.Close() | |
| 664 | + b.StartTimer() | |
| 665 | + for i := 0; i < b.N; i++ { | |
| 666 | + c = p.Get() | |
| 667 | + if err := c.Err(); err != nil { | |
| 668 | + b.Fatal(err) | |
| 669 | + } | |
| 670 | + c.Close() | |
| 671 | + } | |
| 672 | +} | |
| 673 | + | |
| 674 | +func BenchmarkPoolGetPing(b *testing.B) { | |
| 675 | + b.StopTimer() | |
| 676 | + p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2} | |
| 677 | + c := p.Get() | |
| 678 | + if err := c.Err(); err != nil { | |
| 679 | + b.Fatal(err) | |
| 680 | + } | |
| 681 | + c.Close() | |
| 682 | + defer p.Close() | |
| 683 | + b.StartTimer() | |
| 684 | + for i := 0; i < b.N; i++ { | |
| 685 | + c = p.Get() | |
| 686 | + if _, err := c.Do("PING"); err != nil { | |
| 687 | + b.Fatal(err) | |
| 688 | + } | |
| 689 | + c.Close() | |
| 690 | + } | |
| 691 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,31 @@ |
| 1 | +// +build !go1.7 | |
| 2 | + | |
| 3 | +package redis | |
| 4 | + | |
| 5 | +import "crypto/tls" | |
| 6 | + | |
| 7 | +// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case | |
| 8 | +func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config { | |
| 9 | + if cfg == nil { | |
| 10 | + return &tls.Config{InsecureSkipVerify: skipVerify} | |
| 11 | + } | |
| 12 | + return &tls.Config{ | |
| 13 | + Rand: cfg.Rand, | |
| 14 | + Time: cfg.Time, | |
| 15 | + Certificates: cfg.Certificates, | |
| 16 | + NameToCertificate: cfg.NameToCertificate, | |
| 17 | + GetCertificate: cfg.GetCertificate, | |
| 18 | + RootCAs: cfg.RootCAs, | |
| 19 | + NextProtos: cfg.NextProtos, | |
| 20 | + ServerName: cfg.ServerName, | |
| 21 | + ClientAuth: cfg.ClientAuth, | |
| 22 | + ClientCAs: cfg.ClientCAs, | |
| 23 | + InsecureSkipVerify: cfg.InsecureSkipVerify, | |
| 24 | + CipherSuites: cfg.CipherSuites, | |
| 25 | + PreferServerCipherSuites: cfg.PreferServerCipherSuites, | |
| 26 | + ClientSessionCache: cfg.ClientSessionCache, | |
| 27 | + MinVersion: cfg.MinVersion, | |
| 28 | + MaxVersion: cfg.MaxVersion, | |
| 29 | + CurvePreferences: cfg.CurvePreferences, | |
| 30 | + } | |
| 31 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,144 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import "errors" | |
| 18 | + | |
| 19 | +// Subscription represents a subscribe or unsubscribe notification. | |
| 20 | +type Subscription struct { | |
| 21 | + // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe" | |
| 22 | + Kind string | |
| 23 | + | |
| 24 | + // The channel that was changed. | |
| 25 | + Channel string | |
| 26 | + | |
| 27 | + // The current number of subscriptions for connection. | |
| 28 | + Count int | |
| 29 | +} | |
| 30 | + | |
| 31 | +// Message represents a message notification. | |
| 32 | +type Message struct { | |
| 33 | + // The originating channel. | |
| 34 | + Channel string | |
| 35 | + | |
| 36 | + // The message data. | |
| 37 | + Data []byte | |
| 38 | +} | |
| 39 | + | |
| 40 | +// PMessage represents a pmessage notification. | |
| 41 | +type PMessage struct { | |
| 42 | + // The matched pattern. | |
| 43 | + Pattern string | |
| 44 | + | |
| 45 | + // The originating channel. | |
| 46 | + Channel string | |
| 47 | + | |
| 48 | + // The message data. | |
| 49 | + Data []byte | |
| 50 | +} | |
| 51 | + | |
| 52 | +// Pong represents a pubsub pong notification. | |
| 53 | +type Pong struct { | |
| 54 | + Data string | |
| 55 | +} | |
| 56 | + | |
| 57 | +// PubSubConn wraps a Conn with convenience methods for subscribers. | |
| 58 | +type PubSubConn struct { | |
| 59 | + Conn Conn | |
| 60 | +} | |
| 61 | + | |
| 62 | +// Close closes the connection. | |
| 63 | +func (c PubSubConn) Close() error { | |
| 64 | + return c.Conn.Close() | |
| 65 | +} | |
| 66 | + | |
| 67 | +// Subscribe subscribes the connection to the specified channels. | |
| 68 | +func (c PubSubConn) Subscribe(channel ...interface{}) error { | |
| 69 | + c.Conn.Send("SUBSCRIBE", channel...) | |
| 70 | + return c.Conn.Flush() | |
| 71 | +} | |
| 72 | + | |
| 73 | +// PSubscribe subscribes the connection to the given patterns. | |
| 74 | +func (c PubSubConn) PSubscribe(channel ...interface{}) error { | |
| 75 | + c.Conn.Send("PSUBSCRIBE", channel...) | |
| 76 | + return c.Conn.Flush() | |
| 77 | +} | |
| 78 | + | |
| 79 | +// Unsubscribe unsubscribes the connection from the given channels, or from all | |
| 80 | +// of them if none is given. | |
| 81 | +func (c PubSubConn) Unsubscribe(channel ...interface{}) error { | |
| 82 | + c.Conn.Send("UNSUBSCRIBE", channel...) | |
| 83 | + return c.Conn.Flush() | |
| 84 | +} | |
| 85 | + | |
| 86 | +// PUnsubscribe unsubscribes the connection from the given patterns, or from all | |
| 87 | +// of them if none is given. | |
| 88 | +func (c PubSubConn) PUnsubscribe(channel ...interface{}) error { | |
| 89 | + c.Conn.Send("PUNSUBSCRIBE", channel...) | |
| 90 | + return c.Conn.Flush() | |
| 91 | +} | |
| 92 | + | |
| 93 | +// Ping sends a PING to the server with the specified data. | |
| 94 | +// | |
| 95 | +// The connection must be subscribed to at least one channel or pattern when | |
| 96 | +// calling this method. | |
| 97 | +func (c PubSubConn) Ping(data string) error { | |
| 98 | + c.Conn.Send("PING", data) | |
| 99 | + return c.Conn.Flush() | |
| 100 | +} | |
| 101 | + | |
| 102 | +// Receive returns a pushed message as a Subscription, Message, PMessage, Pong | |
| 103 | +// or error. The return value is intended to be used directly in a type switch | |
| 104 | +// as illustrated in the PubSubConn example. | |
| 105 | +func (c PubSubConn) Receive() interface{} { | |
| 106 | + reply, err := Values(c.Conn.Receive()) | |
| 107 | + if err != nil { | |
| 108 | + return err | |
| 109 | + } | |
| 110 | + | |
| 111 | + var kind string | |
| 112 | + reply, err = Scan(reply, &kind) | |
| 113 | + if err != nil { | |
| 114 | + return err | |
| 115 | + } | |
| 116 | + | |
| 117 | + switch kind { | |
| 118 | + case "message": | |
| 119 | + var m Message | |
| 120 | + if _, err := Scan(reply, &m.Channel, &m.Data); err != nil { | |
| 121 | + return err | |
| 122 | + } | |
| 123 | + return m | |
| 124 | + case "pmessage": | |
| 125 | + var pm PMessage | |
| 126 | + if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil { | |
| 127 | + return err | |
| 128 | + } | |
| 129 | + return pm | |
| 130 | + case "subscribe", "psubscribe", "unsubscribe", "punsubscribe": | |
| 131 | + s := Subscription{Kind: kind} | |
| 132 | + if _, err := Scan(reply, &s.Channel, &s.Count); err != nil { | |
| 133 | + return err | |
| 134 | + } | |
| 135 | + return s | |
| 136 | + case "pong": | |
| 137 | + var p Pong | |
| 138 | + if _, err := Scan(reply, &p.Data); err != nil { | |
| 139 | + return err | |
| 140 | + } | |
| 141 | + return p | |
| 142 | + } | |
| 143 | + return errors.New("redigo: unknown pubsub notification") | |
| 144 | +} | ... | ... |
src/github.com/garyburd/redigo/redis/pubsub_example_test.go
0 → 100644
| ... | ... | @@ -0,0 +1,165 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +// +build go1.7 | |
| 16 | + | |
| 17 | +package redis_test | |
| 18 | + | |
| 19 | +import ( | |
| 20 | + "context" | |
| 21 | + "fmt" | |
| 22 | + "time" | |
| 23 | + | |
| 24 | + "github.com/garyburd/redigo/redis" | |
| 25 | +) | |
| 26 | + | |
| 27 | +// listenPubSubChannels listens for messages on Redis pubsub channels. The | |
| 28 | +// onStart function is called after the channels are subscribed. The onMessage | |
| 29 | +// function is called for each message. | |
| 30 | +func listenPubSubChannels(ctx context.Context, redisServerAddr string, | |
| 31 | + onStart func() error, | |
| 32 | + onMessage func(channel string, data []byte) error, | |
| 33 | + channels ...string) error { | |
| 34 | + // A ping is set to the server with this period to test for the health of | |
| 35 | + // the connection and server. | |
| 36 | + const healthCheckPeriod = time.Minute | |
| 37 | + | |
| 38 | + c, err := redis.Dial("tcp", redisServerAddr, | |
| 39 | + // Read timeout on server should be greater than ping period. | |
| 40 | + redis.DialReadTimeout(healthCheckPeriod+10*time.Second), | |
| 41 | + redis.DialWriteTimeout(10*time.Second)) | |
| 42 | + if err != nil { | |
| 43 | + return err | |
| 44 | + } | |
| 45 | + defer c.Close() | |
| 46 | + | |
| 47 | + psc := redis.PubSubConn{Conn: c} | |
| 48 | + | |
| 49 | + if err := psc.Subscribe(redis.Args{}.AddFlat(channels)...); err != nil { | |
| 50 | + return err | |
| 51 | + } | |
| 52 | + | |
| 53 | + done := make(chan error, 1) | |
| 54 | + | |
| 55 | + // Start a goroutine to receive notifications from the server. | |
| 56 | + go func() { | |
| 57 | + for { | |
| 58 | + switch n := psc.Receive().(type) { | |
| 59 | + case error: | |
| 60 | + done <- n | |
| 61 | + return | |
| 62 | + case redis.Message: | |
| 63 | + if err := onMessage(n.Channel, n.Data); err != nil { | |
| 64 | + done <- err | |
| 65 | + return | |
| 66 | + } | |
| 67 | + case redis.Subscription: | |
| 68 | + switch n.Count { | |
| 69 | + case len(channels): | |
| 70 | + // Notify application when all channels are subscribed. | |
| 71 | + if err := onStart(); err != nil { | |
| 72 | + done <- err | |
| 73 | + return | |
| 74 | + } | |
| 75 | + case 0: | |
| 76 | + // Return from the goroutine when all channels are unsubscribed. | |
| 77 | + done <- nil | |
| 78 | + return | |
| 79 | + } | |
| 80 | + } | |
| 81 | + } | |
| 82 | + }() | |
| 83 | + | |
| 84 | + ticker := time.NewTicker(healthCheckPeriod) | |
| 85 | + defer ticker.Stop() | |
| 86 | +loop: | |
| 87 | + for err == nil { | |
| 88 | + select { | |
| 89 | + case <-ticker.C: | |
| 90 | + // Send ping to test health of connection and server. If | |
| 91 | + // corresponding pong is not received, then receive on the | |
| 92 | + // connection will timeout and the receive goroutine will exit. | |
| 93 | + if err = psc.Ping(""); err != nil { | |
| 94 | + break loop | |
| 95 | + } | |
| 96 | + case <-ctx.Done(): | |
| 97 | + break loop | |
| 98 | + case err := <-done: | |
| 99 | + // Return error from the receive goroutine. | |
| 100 | + return err | |
| 101 | + } | |
| 102 | + } | |
| 103 | + | |
| 104 | + // Signal the receiving goroutine to exit by unsubscribing from all channels. | |
| 105 | + psc.Unsubscribe() | |
| 106 | + | |
| 107 | + // Wait for goroutine to complete. | |
| 108 | + return <-done | |
| 109 | +} | |
| 110 | + | |
| 111 | +func publish() { | |
| 112 | + c, err := dial() | |
| 113 | + if err != nil { | |
| 114 | + fmt.Println(err) | |
| 115 | + return | |
| 116 | + } | |
| 117 | + defer c.Close() | |
| 118 | + | |
| 119 | + c.Do("PUBLISH", "c1", "hello") | |
| 120 | + c.Do("PUBLISH", "c2", "world") | |
| 121 | + c.Do("PUBLISH", "c1", "goodbye") | |
| 122 | +} | |
| 123 | + | |
| 124 | +// This example shows how receive pubsub notifications with cancelation and | |
| 125 | +// health checks. | |
| 126 | +func ExamplePubSubConn() { | |
| 127 | + redisServerAddr, err := serverAddr() | |
| 128 | + if err != nil { | |
| 129 | + fmt.Println(err) | |
| 130 | + return | |
| 131 | + } | |
| 132 | + | |
| 133 | + ctx, cancel := context.WithCancel(context.Background()) | |
| 134 | + | |
| 135 | + err = listenPubSubChannels(ctx, | |
| 136 | + redisServerAddr, | |
| 137 | + func() error { | |
| 138 | + // The start callback is a good place to backfill missed | |
| 139 | + // notifications. For the purpose of this example, a goroutine is | |
| 140 | + // started to send notifications. | |
| 141 | + go publish() | |
| 142 | + return nil | |
| 143 | + }, | |
| 144 | + func(channel string, message []byte) error { | |
| 145 | + fmt.Printf("channel: %s, message: %s\n", channel, message) | |
| 146 | + | |
| 147 | + // For the purpose of this example, cancel the listener's context | |
| 148 | + // after receiving last message sent by publish(). | |
| 149 | + if string(message) == "goodbye" { | |
| 150 | + cancel() | |
| 151 | + } | |
| 152 | + return nil | |
| 153 | + }, | |
| 154 | + "c1", "c2") | |
| 155 | + | |
| 156 | + if err != nil { | |
| 157 | + fmt.Println(err) | |
| 158 | + return | |
| 159 | + } | |
| 160 | + | |
| 161 | + // Output: | |
| 162 | + // channel: c1, message: hello | |
| 163 | + // channel: c2, message: world | |
| 164 | + // channel: c1, message: goodbye | |
| 165 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,67 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "reflect" | |
| 19 | + "testing" | |
| 20 | + | |
| 21 | + "github.com/garyburd/redigo/redis" | |
| 22 | +) | |
| 23 | + | |
| 24 | +func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) { | |
| 25 | + actual := c.Receive() | |
| 26 | + if !reflect.DeepEqual(actual, expected) { | |
| 27 | + t.Errorf("%s = %v, want %v", message, actual, expected) | |
| 28 | + } | |
| 29 | +} | |
| 30 | + | |
| 31 | +func TestPushed(t *testing.T) { | |
| 32 | + pc, err := redis.DialDefaultServer() | |
| 33 | + if err != nil { | |
| 34 | + t.Fatalf("error connection to database, %v", err) | |
| 35 | + } | |
| 36 | + defer pc.Close() | |
| 37 | + | |
| 38 | + sc, err := redis.DialDefaultServer() | |
| 39 | + if err != nil { | |
| 40 | + t.Fatalf("error connection to database, %v", err) | |
| 41 | + } | |
| 42 | + defer sc.Close() | |
| 43 | + | |
| 44 | + c := redis.PubSubConn{Conn: sc} | |
| 45 | + | |
| 46 | + c.Subscribe("c1") | |
| 47 | + expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1}) | |
| 48 | + c.Subscribe("c2") | |
| 49 | + expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2}) | |
| 50 | + c.PSubscribe("p1") | |
| 51 | + expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3}) | |
| 52 | + c.PSubscribe("p2") | |
| 53 | + expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4}) | |
| 54 | + c.PUnsubscribe() | |
| 55 | + expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3}) | |
| 56 | + expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2}) | |
| 57 | + | |
| 58 | + pc.Do("PUBLISH", "c1", "hello") | |
| 59 | + expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")}) | |
| 60 | + | |
| 61 | + c.Ping("hello") | |
| 62 | + expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"}) | |
| 63 | + | |
| 64 | + c.Conn.Send("PING") | |
| 65 | + c.Conn.Flush() | |
| 66 | + expectPushed(t, c, `Send("PING")`, redis.Pong{}) | |
| 67 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,61 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +// Error represents an error returned in a command reply. | |
| 18 | +type Error string | |
| 19 | + | |
| 20 | +func (err Error) Error() string { return string(err) } | |
| 21 | + | |
| 22 | +// Conn represents a connection to a Redis server. | |
| 23 | +type Conn interface { | |
| 24 | + // Close closes the connection. | |
| 25 | + Close() error | |
| 26 | + | |
| 27 | + // Err returns a non-nil value when the connection is not usable. | |
| 28 | + Err() error | |
| 29 | + | |
| 30 | + // Do sends a command to the server and returns the received reply. | |
| 31 | + Do(commandName string, args ...interface{}) (reply interface{}, err error) | |
| 32 | + | |
| 33 | + // Send writes the command to the client's output buffer. | |
| 34 | + Send(commandName string, args ...interface{}) error | |
| 35 | + | |
| 36 | + // Flush flushes the output buffer to the Redis server. | |
| 37 | + Flush() error | |
| 38 | + | |
| 39 | + // Receive receives a single reply from the Redis server | |
| 40 | + Receive() (reply interface{}, err error) | |
| 41 | +} | |
| 42 | + | |
| 43 | +// Argument is the interface implemented by an object which wants to control how | |
| 44 | +// the object is converted to Redis bulk strings. | |
| 45 | +type Argument interface { | |
| 46 | + // RedisArg returns a value to be encoded as a bulk string per the | |
| 47 | + // conversions listed in the section 'Executing Commands'. | |
| 48 | + // Implementations should typically return a []byte or string. | |
| 49 | + RedisArg() interface{} | |
| 50 | +} | |
| 51 | + | |
| 52 | +// Scanner is implemented by an object which wants to control its value is | |
| 53 | +// interpreted when read from Redis. | |
| 54 | +type Scanner interface { | |
| 55 | + // RedisScan assigns a value from a Redis value. The argument src is one of | |
| 56 | + // the reply types listed in the section `Executing Commands`. | |
| 57 | + // | |
| 58 | + // An error should be returned if the value cannot be stored without | |
| 59 | + // loss of information. | |
| 60 | + RedisScan(src interface{}) error | |
| 61 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,479 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "errors" | |
| 19 | + "fmt" | |
| 20 | + "strconv" | |
| 21 | +) | |
| 22 | + | |
| 23 | +// ErrNil indicates that a reply value is nil. | |
| 24 | +var ErrNil = errors.New("redigo: nil returned") | |
| 25 | + | |
| 26 | +// Int is a helper that converts a command reply to an integer. If err is not | |
| 27 | +// equal to nil, then Int returns 0, err. Otherwise, Int converts the | |
| 28 | +// reply to an int as follows: | |
| 29 | +// | |
| 30 | +// Reply type Result | |
| 31 | +// integer int(reply), nil | |
| 32 | +// bulk string parsed reply, nil | |
| 33 | +// nil 0, ErrNil | |
| 34 | +// other 0, error | |
| 35 | +func Int(reply interface{}, err error) (int, error) { | |
| 36 | + if err != nil { | |
| 37 | + return 0, err | |
| 38 | + } | |
| 39 | + switch reply := reply.(type) { | |
| 40 | + case int64: | |
| 41 | + x := int(reply) | |
| 42 | + if int64(x) != reply { | |
| 43 | + return 0, strconv.ErrRange | |
| 44 | + } | |
| 45 | + return x, nil | |
| 46 | + case []byte: | |
| 47 | + n, err := strconv.ParseInt(string(reply), 10, 0) | |
| 48 | + return int(n), err | |
| 49 | + case nil: | |
| 50 | + return 0, ErrNil | |
| 51 | + case Error: | |
| 52 | + return 0, reply | |
| 53 | + } | |
| 54 | + return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply) | |
| 55 | +} | |
| 56 | + | |
| 57 | +// Int64 is a helper that converts a command reply to 64 bit integer. If err is | |
| 58 | +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the | |
| 59 | +// reply to an int64 as follows: | |
| 60 | +// | |
| 61 | +// Reply type Result | |
| 62 | +// integer reply, nil | |
| 63 | +// bulk string parsed reply, nil | |
| 64 | +// nil 0, ErrNil | |
| 65 | +// other 0, error | |
| 66 | +func Int64(reply interface{}, err error) (int64, error) { | |
| 67 | + if err != nil { | |
| 68 | + return 0, err | |
| 69 | + } | |
| 70 | + switch reply := reply.(type) { | |
| 71 | + case int64: | |
| 72 | + return reply, nil | |
| 73 | + case []byte: | |
| 74 | + n, err := strconv.ParseInt(string(reply), 10, 64) | |
| 75 | + return n, err | |
| 76 | + case nil: | |
| 77 | + return 0, ErrNil | |
| 78 | + case Error: | |
| 79 | + return 0, reply | |
| 80 | + } | |
| 81 | + return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply) | |
| 82 | +} | |
| 83 | + | |
| 84 | +var errNegativeInt = errors.New("redigo: unexpected value for Uint64") | |
| 85 | + | |
| 86 | +// Uint64 is a helper that converts a command reply to 64 bit integer. If err is | |
| 87 | +// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the | |
| 88 | +// reply to an int64 as follows: | |
| 89 | +// | |
| 90 | +// Reply type Result | |
| 91 | +// integer reply, nil | |
| 92 | +// bulk string parsed reply, nil | |
| 93 | +// nil 0, ErrNil | |
| 94 | +// other 0, error | |
| 95 | +func Uint64(reply interface{}, err error) (uint64, error) { | |
| 96 | + if err != nil { | |
| 97 | + return 0, err | |
| 98 | + } | |
| 99 | + switch reply := reply.(type) { | |
| 100 | + case int64: | |
| 101 | + if reply < 0 { | |
| 102 | + return 0, errNegativeInt | |
| 103 | + } | |
| 104 | + return uint64(reply), nil | |
| 105 | + case []byte: | |
| 106 | + n, err := strconv.ParseUint(string(reply), 10, 64) | |
| 107 | + return n, err | |
| 108 | + case nil: | |
| 109 | + return 0, ErrNil | |
| 110 | + case Error: | |
| 111 | + return 0, reply | |
| 112 | + } | |
| 113 | + return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply) | |
| 114 | +} | |
| 115 | + | |
| 116 | +// Float64 is a helper that converts a command reply to 64 bit float. If err is | |
| 117 | +// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts | |
| 118 | +// the reply to an int as follows: | |
| 119 | +// | |
| 120 | +// Reply type Result | |
| 121 | +// bulk string parsed reply, nil | |
| 122 | +// nil 0, ErrNil | |
| 123 | +// other 0, error | |
| 124 | +func Float64(reply interface{}, err error) (float64, error) { | |
| 125 | + if err != nil { | |
| 126 | + return 0, err | |
| 127 | + } | |
| 128 | + switch reply := reply.(type) { | |
| 129 | + case []byte: | |
| 130 | + n, err := strconv.ParseFloat(string(reply), 64) | |
| 131 | + return n, err | |
| 132 | + case nil: | |
| 133 | + return 0, ErrNil | |
| 134 | + case Error: | |
| 135 | + return 0, reply | |
| 136 | + } | |
| 137 | + return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply) | |
| 138 | +} | |
| 139 | + | |
| 140 | +// String is a helper that converts a command reply to a string. If err is not | |
| 141 | +// equal to nil, then String returns "", err. Otherwise String converts the | |
| 142 | +// reply to a string as follows: | |
| 143 | +// | |
| 144 | +// Reply type Result | |
| 145 | +// bulk string string(reply), nil | |
| 146 | +// simple string reply, nil | |
| 147 | +// nil "", ErrNil | |
| 148 | +// other "", error | |
| 149 | +func String(reply interface{}, err error) (string, error) { | |
| 150 | + if err != nil { | |
| 151 | + return "", err | |
| 152 | + } | |
| 153 | + switch reply := reply.(type) { | |
| 154 | + case []byte: | |
| 155 | + return string(reply), nil | |
| 156 | + case string: | |
| 157 | + return reply, nil | |
| 158 | + case nil: | |
| 159 | + return "", ErrNil | |
| 160 | + case Error: | |
| 161 | + return "", reply | |
| 162 | + } | |
| 163 | + return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply) | |
| 164 | +} | |
| 165 | + | |
| 166 | +// Bytes is a helper that converts a command reply to a slice of bytes. If err | |
| 167 | +// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts | |
| 168 | +// the reply to a slice of bytes as follows: | |
| 169 | +// | |
| 170 | +// Reply type Result | |
| 171 | +// bulk string reply, nil | |
| 172 | +// simple string []byte(reply), nil | |
| 173 | +// nil nil, ErrNil | |
| 174 | +// other nil, error | |
| 175 | +func Bytes(reply interface{}, err error) ([]byte, error) { | |
| 176 | + if err != nil { | |
| 177 | + return nil, err | |
| 178 | + } | |
| 179 | + switch reply := reply.(type) { | |
| 180 | + case []byte: | |
| 181 | + return reply, nil | |
| 182 | + case string: | |
| 183 | + return []byte(reply), nil | |
| 184 | + case nil: | |
| 185 | + return nil, ErrNil | |
| 186 | + case Error: | |
| 187 | + return nil, reply | |
| 188 | + } | |
| 189 | + return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply) | |
| 190 | +} | |
| 191 | + | |
| 192 | +// Bool is a helper that converts a command reply to a boolean. If err is not | |
| 193 | +// equal to nil, then Bool returns false, err. Otherwise Bool converts the | |
| 194 | +// reply to boolean as follows: | |
| 195 | +// | |
| 196 | +// Reply type Result | |
| 197 | +// integer value != 0, nil | |
| 198 | +// bulk string strconv.ParseBool(reply) | |
| 199 | +// nil false, ErrNil | |
| 200 | +// other false, error | |
| 201 | +func Bool(reply interface{}, err error) (bool, error) { | |
| 202 | + if err != nil { | |
| 203 | + return false, err | |
| 204 | + } | |
| 205 | + switch reply := reply.(type) { | |
| 206 | + case int64: | |
| 207 | + return reply != 0, nil | |
| 208 | + case []byte: | |
| 209 | + return strconv.ParseBool(string(reply)) | |
| 210 | + case nil: | |
| 211 | + return false, ErrNil | |
| 212 | + case Error: | |
| 213 | + return false, reply | |
| 214 | + } | |
| 215 | + return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply) | |
| 216 | +} | |
| 217 | + | |
| 218 | +// MultiBulk is a helper that converts an array command reply to a []interface{}. | |
| 219 | +// | |
| 220 | +// Deprecated: Use Values instead. | |
| 221 | +func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) } | |
| 222 | + | |
| 223 | +// Values is a helper that converts an array command reply to a []interface{}. | |
| 224 | +// If err is not equal to nil, then Values returns nil, err. Otherwise, Values | |
| 225 | +// converts the reply as follows: | |
| 226 | +// | |
| 227 | +// Reply type Result | |
| 228 | +// array reply, nil | |
| 229 | +// nil nil, ErrNil | |
| 230 | +// other nil, error | |
| 231 | +func Values(reply interface{}, err error) ([]interface{}, error) { | |
| 232 | + if err != nil { | |
| 233 | + return nil, err | |
| 234 | + } | |
| 235 | + switch reply := reply.(type) { | |
| 236 | + case []interface{}: | |
| 237 | + return reply, nil | |
| 238 | + case nil: | |
| 239 | + return nil, ErrNil | |
| 240 | + case Error: | |
| 241 | + return nil, reply | |
| 242 | + } | |
| 243 | + return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply) | |
| 244 | +} | |
| 245 | + | |
| 246 | +func sliceHelper(reply interface{}, err error, name string, makeSlice func(int), assign func(int, interface{}) error) error { | |
| 247 | + if err != nil { | |
| 248 | + return err | |
| 249 | + } | |
| 250 | + switch reply := reply.(type) { | |
| 251 | + case []interface{}: | |
| 252 | + makeSlice(len(reply)) | |
| 253 | + for i := range reply { | |
| 254 | + if reply[i] == nil { | |
| 255 | + continue | |
| 256 | + } | |
| 257 | + if err := assign(i, reply[i]); err != nil { | |
| 258 | + return err | |
| 259 | + } | |
| 260 | + } | |
| 261 | + return nil | |
| 262 | + case nil: | |
| 263 | + return ErrNil | |
| 264 | + case Error: | |
| 265 | + return reply | |
| 266 | + } | |
| 267 | + return fmt.Errorf("redigo: unexpected type for %s, got type %T", name, reply) | |
| 268 | +} | |
| 269 | + | |
| 270 | +// Float64s is a helper that converts an array command reply to a []float64. If | |
| 271 | +// err is not equal to nil, then Float64s returns nil, err. Nil array items are | |
| 272 | +// converted to 0 in the output slice. Floats64 returns an error if an array | |
| 273 | +// item is not a bulk string or nil. | |
| 274 | +func Float64s(reply interface{}, err error) ([]float64, error) { | |
| 275 | + var result []float64 | |
| 276 | + err = sliceHelper(reply, err, "Float64s", func(n int) { result = make([]float64, n) }, func(i int, v interface{}) error { | |
| 277 | + p, ok := v.([]byte) | |
| 278 | + if !ok { | |
| 279 | + return fmt.Errorf("redigo: unexpected element type for Floats64, got type %T", v) | |
| 280 | + } | |
| 281 | + f, err := strconv.ParseFloat(string(p), 64) | |
| 282 | + result[i] = f | |
| 283 | + return err | |
| 284 | + }) | |
| 285 | + return result, err | |
| 286 | +} | |
| 287 | + | |
| 288 | +// Strings is a helper that converts an array command reply to a []string. If | |
| 289 | +// err is not equal to nil, then Strings returns nil, err. Nil array items are | |
| 290 | +// converted to "" in the output slice. Strings returns an error if an array | |
| 291 | +// item is not a bulk string or nil. | |
| 292 | +func Strings(reply interface{}, err error) ([]string, error) { | |
| 293 | + var result []string | |
| 294 | + err = sliceHelper(reply, err, "Strings", func(n int) { result = make([]string, n) }, func(i int, v interface{}) error { | |
| 295 | + switch v := v.(type) { | |
| 296 | + case string: | |
| 297 | + result[i] = v | |
| 298 | + return nil | |
| 299 | + case []byte: | |
| 300 | + result[i] = string(v) | |
| 301 | + return nil | |
| 302 | + default: | |
| 303 | + return fmt.Errorf("redigo: unexpected element type for Strings, got type %T", v) | |
| 304 | + } | |
| 305 | + }) | |
| 306 | + return result, err | |
| 307 | +} | |
| 308 | + | |
| 309 | +// ByteSlices is a helper that converts an array command reply to a [][]byte. | |
| 310 | +// If err is not equal to nil, then ByteSlices returns nil, err. Nil array | |
| 311 | +// items are stay nil. ByteSlices returns an error if an array item is not a | |
| 312 | +// bulk string or nil. | |
| 313 | +func ByteSlices(reply interface{}, err error) ([][]byte, error) { | |
| 314 | + var result [][]byte | |
| 315 | + err = sliceHelper(reply, err, "ByteSlices", func(n int) { result = make([][]byte, n) }, func(i int, v interface{}) error { | |
| 316 | + p, ok := v.([]byte) | |
| 317 | + if !ok { | |
| 318 | + return fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", v) | |
| 319 | + } | |
| 320 | + result[i] = p | |
| 321 | + return nil | |
| 322 | + }) | |
| 323 | + return result, err | |
| 324 | +} | |
| 325 | + | |
| 326 | +// Int64s is a helper that converts an array command reply to a []int64. | |
| 327 | +// If err is not equal to nil, then Int64s returns nil, err. Nil array | |
| 328 | +// items are stay nil. Int64s returns an error if an array item is not a | |
| 329 | +// bulk string or nil. | |
| 330 | +func Int64s(reply interface{}, err error) ([]int64, error) { | |
| 331 | + var result []int64 | |
| 332 | + err = sliceHelper(reply, err, "Int64s", func(n int) { result = make([]int64, n) }, func(i int, v interface{}) error { | |
| 333 | + switch v := v.(type) { | |
| 334 | + case int64: | |
| 335 | + result[i] = v | |
| 336 | + return nil | |
| 337 | + case []byte: | |
| 338 | + n, err := strconv.ParseInt(string(v), 10, 64) | |
| 339 | + result[i] = n | |
| 340 | + return err | |
| 341 | + default: | |
| 342 | + return fmt.Errorf("redigo: unexpected element type for Int64s, got type %T", v) | |
| 343 | + } | |
| 344 | + }) | |
| 345 | + return result, err | |
| 346 | +} | |
| 347 | + | |
| 348 | +// Ints is a helper that converts an array command reply to a []in. | |
| 349 | +// If err is not equal to nil, then Ints returns nil, err. Nil array | |
| 350 | +// items are stay nil. Ints returns an error if an array item is not a | |
| 351 | +// bulk string or nil. | |
| 352 | +func Ints(reply interface{}, err error) ([]int, error) { | |
| 353 | + var result []int | |
| 354 | + err = sliceHelper(reply, err, "Ints", func(n int) { result = make([]int, n) }, func(i int, v interface{}) error { | |
| 355 | + switch v := v.(type) { | |
| 356 | + case int64: | |
| 357 | + n := int(v) | |
| 358 | + if int64(n) != v { | |
| 359 | + return strconv.ErrRange | |
| 360 | + } | |
| 361 | + result[i] = n | |
| 362 | + return nil | |
| 363 | + case []byte: | |
| 364 | + n, err := strconv.Atoi(string(v)) | |
| 365 | + result[i] = n | |
| 366 | + return err | |
| 367 | + default: | |
| 368 | + return fmt.Errorf("redigo: unexpected element type for Ints, got type %T", v) | |
| 369 | + } | |
| 370 | + }) | |
| 371 | + return result, err | |
| 372 | +} | |
| 373 | + | |
| 374 | +// StringMap is a helper that converts an array of strings (alternating key, value) | |
| 375 | +// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format. | |
| 376 | +// Requires an even number of values in result. | |
| 377 | +func StringMap(result interface{}, err error) (map[string]string, error) { | |
| 378 | + values, err := Values(result, err) | |
| 379 | + if err != nil { | |
| 380 | + return nil, err | |
| 381 | + } | |
| 382 | + if len(values)%2 != 0 { | |
| 383 | + return nil, errors.New("redigo: StringMap expects even number of values result") | |
| 384 | + } | |
| 385 | + m := make(map[string]string, len(values)/2) | |
| 386 | + for i := 0; i < len(values); i += 2 { | |
| 387 | + key, okKey := values[i].([]byte) | |
| 388 | + value, okValue := values[i+1].([]byte) | |
| 389 | + if !okKey || !okValue { | |
| 390 | + return nil, errors.New("redigo: StringMap key not a bulk string value") | |
| 391 | + } | |
| 392 | + m[string(key)] = string(value) | |
| 393 | + } | |
| 394 | + return m, nil | |
| 395 | +} | |
| 396 | + | |
| 397 | +// IntMap is a helper that converts an array of strings (alternating key, value) | |
| 398 | +// into a map[string]int. The HGETALL commands return replies in this format. | |
| 399 | +// Requires an even number of values in result. | |
| 400 | +func IntMap(result interface{}, err error) (map[string]int, error) { | |
| 401 | + values, err := Values(result, err) | |
| 402 | + if err != nil { | |
| 403 | + return nil, err | |
| 404 | + } | |
| 405 | + if len(values)%2 != 0 { | |
| 406 | + return nil, errors.New("redigo: IntMap expects even number of values result") | |
| 407 | + } | |
| 408 | + m := make(map[string]int, len(values)/2) | |
| 409 | + for i := 0; i < len(values); i += 2 { | |
| 410 | + key, ok := values[i].([]byte) | |
| 411 | + if !ok { | |
| 412 | + return nil, errors.New("redigo: IntMap key not a bulk string value") | |
| 413 | + } | |
| 414 | + value, err := Int(values[i+1], nil) | |
| 415 | + if err != nil { | |
| 416 | + return nil, err | |
| 417 | + } | |
| 418 | + m[string(key)] = value | |
| 419 | + } | |
| 420 | + return m, nil | |
| 421 | +} | |
| 422 | + | |
| 423 | +// Int64Map is a helper that converts an array of strings (alternating key, value) | |
| 424 | +// into a map[string]int64. The HGETALL commands return replies in this format. | |
| 425 | +// Requires an even number of values in result. | |
| 426 | +func Int64Map(result interface{}, err error) (map[string]int64, error) { | |
| 427 | + values, err := Values(result, err) | |
| 428 | + if err != nil { | |
| 429 | + return nil, err | |
| 430 | + } | |
| 431 | + if len(values)%2 != 0 { | |
| 432 | + return nil, errors.New("redigo: Int64Map expects even number of values result") | |
| 433 | + } | |
| 434 | + m := make(map[string]int64, len(values)/2) | |
| 435 | + for i := 0; i < len(values); i += 2 { | |
| 436 | + key, ok := values[i].([]byte) | |
| 437 | + if !ok { | |
| 438 | + return nil, errors.New("redigo: Int64Map key not a bulk string value") | |
| 439 | + } | |
| 440 | + value, err := Int64(values[i+1], nil) | |
| 441 | + if err != nil { | |
| 442 | + return nil, err | |
| 443 | + } | |
| 444 | + m[string(key)] = value | |
| 445 | + } | |
| 446 | + return m, nil | |
| 447 | +} | |
| 448 | + | |
| 449 | +// Positions is a helper that converts an array of positions (lat, long) | |
| 450 | +// into a [][2]float64. The GEOPOS command returns replies in this format. | |
| 451 | +func Positions(result interface{}, err error) ([]*[2]float64, error) { | |
| 452 | + values, err := Values(result, err) | |
| 453 | + if err != nil { | |
| 454 | + return nil, err | |
| 455 | + } | |
| 456 | + positions := make([]*[2]float64, len(values)) | |
| 457 | + for i := range values { | |
| 458 | + if values[i] == nil { | |
| 459 | + continue | |
| 460 | + } | |
| 461 | + p, ok := values[i].([]interface{}) | |
| 462 | + if !ok { | |
| 463 | + return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i]) | |
| 464 | + } | |
| 465 | + if len(p) != 2 { | |
| 466 | + return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p)) | |
| 467 | + } | |
| 468 | + lat, err := Float64(p[0], nil) | |
| 469 | + if err != nil { | |
| 470 | + return nil, err | |
| 471 | + } | |
| 472 | + long, err := Float64(p[1], nil) | |
| 473 | + if err != nil { | |
| 474 | + return nil, err | |
| 475 | + } | |
| 476 | + positions[i] = &[2]float64{lat, long} | |
| 477 | + } | |
| 478 | + return positions, nil | |
| 479 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,209 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "fmt" | |
| 19 | + "reflect" | |
| 20 | + "testing" | |
| 21 | + | |
| 22 | + "github.com/garyburd/redigo/redis" | |
| 23 | +) | |
| 24 | + | |
| 25 | +type valueError struct { | |
| 26 | + v interface{} | |
| 27 | + err error | |
| 28 | +} | |
| 29 | + | |
| 30 | +func ve(v interface{}, err error) valueError { | |
| 31 | + return valueError{v, err} | |
| 32 | +} | |
| 33 | + | |
| 34 | +var replyTests = []struct { | |
| 35 | + name interface{} | |
| 36 | + actual valueError | |
| 37 | + expected valueError | |
| 38 | +}{ | |
| 39 | + { | |
| 40 | + "ints([[]byte, []byte])", | |
| 41 | + ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)), | |
| 42 | + ve([]int{4, 5}, nil), | |
| 43 | + }, | |
| 44 | + { | |
| 45 | + "ints([nt64, int64])", | |
| 46 | + ve(redis.Ints([]interface{}{int64(4), int64(5)}, nil)), | |
| 47 | + ve([]int{4, 5}, nil), | |
| 48 | + }, | |
| 49 | + { | |
| 50 | + "ints([[]byte, nil, []byte])", | |
| 51 | + ve(redis.Ints([]interface{}{[]byte("4"), nil, []byte("5")}, nil)), | |
| 52 | + ve([]int{4, 0, 5}, nil), | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + "ints(nil)", | |
| 56 | + ve(redis.Ints(nil, nil)), | |
| 57 | + ve([]int(nil), redis.ErrNil), | |
| 58 | + }, | |
| 59 | + { | |
| 60 | + "int64s([[]byte, []byte])", | |
| 61 | + ve(redis.Int64s([]interface{}{[]byte("4"), []byte("5")}, nil)), | |
| 62 | + ve([]int64{4, 5}, nil), | |
| 63 | + }, | |
| 64 | + { | |
| 65 | + "int64s([int64, int64])", | |
| 66 | + ve(redis.Int64s([]interface{}{int64(4), int64(5)}, nil)), | |
| 67 | + ve([]int64{4, 5}, nil), | |
| 68 | + }, | |
| 69 | + { | |
| 70 | + "strings([[]byte, []bytev2])", | |
| 71 | + ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)), | |
| 72 | + ve([]string{"v1", "v2"}, nil), | |
| 73 | + }, | |
| 74 | + { | |
| 75 | + "strings([string, string])", | |
| 76 | + ve(redis.Strings([]interface{}{"v1", "v2"}, nil)), | |
| 77 | + ve([]string{"v1", "v2"}, nil), | |
| 78 | + }, | |
| 79 | + { | |
| 80 | + "byteslices([v1, v2])", | |
| 81 | + ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)), | |
| 82 | + ve([][]byte{[]byte("v1"), []byte("v2")}, nil), | |
| 83 | + }, | |
| 84 | + { | |
| 85 | + "float64s([v1, v2])", | |
| 86 | + ve(redis.Float64s([]interface{}{[]byte("1.234"), []byte("5.678")}, nil)), | |
| 87 | + ve([]float64{1.234, 5.678}, nil), | |
| 88 | + }, | |
| 89 | + { | |
| 90 | + "values([v1, v2])", | |
| 91 | + ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)), | |
| 92 | + ve([]interface{}{[]byte("v1"), []byte("v2")}, nil), | |
| 93 | + }, | |
| 94 | + { | |
| 95 | + "values(nil)", | |
| 96 | + ve(redis.Values(nil, nil)), | |
| 97 | + ve([]interface{}(nil), redis.ErrNil), | |
| 98 | + }, | |
| 99 | + { | |
| 100 | + "float64(1.0)", | |
| 101 | + ve(redis.Float64([]byte("1.0"), nil)), | |
| 102 | + ve(float64(1.0), nil), | |
| 103 | + }, | |
| 104 | + { | |
| 105 | + "float64(nil)", | |
| 106 | + ve(redis.Float64(nil, nil)), | |
| 107 | + ve(float64(0.0), redis.ErrNil), | |
| 108 | + }, | |
| 109 | + { | |
| 110 | + "uint64(1)", | |
| 111 | + ve(redis.Uint64(int64(1), nil)), | |
| 112 | + ve(uint64(1), nil), | |
| 113 | + }, | |
| 114 | + { | |
| 115 | + "uint64(-1)", | |
| 116 | + ve(redis.Uint64(int64(-1), nil)), | |
| 117 | + ve(uint64(0), redis.ErrNegativeInt), | |
| 118 | + }, | |
| 119 | + { | |
| 120 | + "positions([[1, 2], nil, [3, 4]])", | |
| 121 | + ve(redis.Positions([]interface{}{[]interface{}{[]byte("1"), []byte("2")}, nil, []interface{}{[]byte("3"), []byte("4")}}, nil)), | |
| 122 | + ve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil), | |
| 123 | + }, | |
| 124 | +} | |
| 125 | + | |
| 126 | +func TestReply(t *testing.T) { | |
| 127 | + for _, rt := range replyTests { | |
| 128 | + if rt.actual.err != rt.expected.err { | |
| 129 | + t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err) | |
| 130 | + continue | |
| 131 | + } | |
| 132 | + if !reflect.DeepEqual(rt.actual.v, rt.expected.v) { | |
| 133 | + t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v) | |
| 134 | + } | |
| 135 | + } | |
| 136 | +} | |
| 137 | + | |
| 138 | +// dial wraps DialDefaultServer() with a more suitable function name for examples. | |
| 139 | +func dial() (redis.Conn, error) { | |
| 140 | + return redis.DialDefaultServer() | |
| 141 | +} | |
| 142 | + | |
| 143 | +// serverAddr wraps DefaultServerAddr() with a more suitable function name for examples. | |
| 144 | +func serverAddr() (string, error) { | |
| 145 | + return redis.DefaultServerAddr() | |
| 146 | +} | |
| 147 | + | |
| 148 | +func ExampleBool() { | |
| 149 | + c, err := dial() | |
| 150 | + if err != nil { | |
| 151 | + fmt.Println(err) | |
| 152 | + return | |
| 153 | + } | |
| 154 | + defer c.Close() | |
| 155 | + | |
| 156 | + c.Do("SET", "foo", 1) | |
| 157 | + exists, _ := redis.Bool(c.Do("EXISTS", "foo")) | |
| 158 | + fmt.Printf("%#v\n", exists) | |
| 159 | + // Output: | |
| 160 | + // true | |
| 161 | +} | |
| 162 | + | |
| 163 | +func ExampleInt() { | |
| 164 | + c, err := dial() | |
| 165 | + if err != nil { | |
| 166 | + fmt.Println(err) | |
| 167 | + return | |
| 168 | + } | |
| 169 | + defer c.Close() | |
| 170 | + | |
| 171 | + c.Do("SET", "k1", 1) | |
| 172 | + n, _ := redis.Int(c.Do("GET", "k1")) | |
| 173 | + fmt.Printf("%#v\n", n) | |
| 174 | + n, _ = redis.Int(c.Do("INCR", "k1")) | |
| 175 | + fmt.Printf("%#v\n", n) | |
| 176 | + // Output: | |
| 177 | + // 1 | |
| 178 | + // 2 | |
| 179 | +} | |
| 180 | + | |
| 181 | +func ExampleInts() { | |
| 182 | + c, err := dial() | |
| 183 | + if err != nil { | |
| 184 | + fmt.Println(err) | |
| 185 | + return | |
| 186 | + } | |
| 187 | + defer c.Close() | |
| 188 | + | |
| 189 | + c.Do("SADD", "set_with_integers", 4, 5, 6) | |
| 190 | + ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers")) | |
| 191 | + fmt.Printf("%#v\n", ints) | |
| 192 | + // Output: | |
| 193 | + // []int{4, 5, 6} | |
| 194 | +} | |
| 195 | + | |
| 196 | +func ExampleString() { | |
| 197 | + c, err := dial() | |
| 198 | + if err != nil { | |
| 199 | + fmt.Println(err) | |
| 200 | + return | |
| 201 | + } | |
| 202 | + defer c.Close() | |
| 203 | + | |
| 204 | + c.Do("SET", "hello", "world") | |
| 205 | + s, err := redis.String(c.Do("GET", "hello")) | |
| 206 | + fmt.Printf("%#v\n", s) | |
| 207 | + // Output: | |
| 208 | + // "world" | |
| 209 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,585 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "errors" | |
| 19 | + "fmt" | |
| 20 | + "reflect" | |
| 21 | + "strconv" | |
| 22 | + "strings" | |
| 23 | + "sync" | |
| 24 | +) | |
| 25 | + | |
| 26 | +func ensureLen(d reflect.Value, n int) { | |
| 27 | + if n > d.Cap() { | |
| 28 | + d.Set(reflect.MakeSlice(d.Type(), n, n)) | |
| 29 | + } else { | |
| 30 | + d.SetLen(n) | |
| 31 | + } | |
| 32 | +} | |
| 33 | + | |
| 34 | +func cannotConvert(d reflect.Value, s interface{}) error { | |
| 35 | + var sname string | |
| 36 | + switch s.(type) { | |
| 37 | + case string: | |
| 38 | + sname = "Redis simple string" | |
| 39 | + case Error: | |
| 40 | + sname = "Redis error" | |
| 41 | + case int64: | |
| 42 | + sname = "Redis integer" | |
| 43 | + case []byte: | |
| 44 | + sname = "Redis bulk string" | |
| 45 | + case []interface{}: | |
| 46 | + sname = "Redis array" | |
| 47 | + default: | |
| 48 | + sname = reflect.TypeOf(s).String() | |
| 49 | + } | |
| 50 | + return fmt.Errorf("cannot convert from %s to %s", sname, d.Type()) | |
| 51 | +} | |
| 52 | + | |
| 53 | +func convertAssignBulkString(d reflect.Value, s []byte) (err error) { | |
| 54 | + switch d.Type().Kind() { | |
| 55 | + case reflect.Float32, reflect.Float64: | |
| 56 | + var x float64 | |
| 57 | + x, err = strconv.ParseFloat(string(s), d.Type().Bits()) | |
| 58 | + d.SetFloat(x) | |
| 59 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 60 | + var x int64 | |
| 61 | + x, err = strconv.ParseInt(string(s), 10, d.Type().Bits()) | |
| 62 | + d.SetInt(x) | |
| 63 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
| 64 | + var x uint64 | |
| 65 | + x, err = strconv.ParseUint(string(s), 10, d.Type().Bits()) | |
| 66 | + d.SetUint(x) | |
| 67 | + case reflect.Bool: | |
| 68 | + var x bool | |
| 69 | + x, err = strconv.ParseBool(string(s)) | |
| 70 | + d.SetBool(x) | |
| 71 | + case reflect.String: | |
| 72 | + d.SetString(string(s)) | |
| 73 | + case reflect.Slice: | |
| 74 | + if d.Type().Elem().Kind() != reflect.Uint8 { | |
| 75 | + err = cannotConvert(d, s) | |
| 76 | + } else { | |
| 77 | + d.SetBytes(s) | |
| 78 | + } | |
| 79 | + default: | |
| 80 | + err = cannotConvert(d, s) | |
| 81 | + } | |
| 82 | + return | |
| 83 | +} | |
| 84 | + | |
| 85 | +func convertAssignInt(d reflect.Value, s int64) (err error) { | |
| 86 | + switch d.Type().Kind() { | |
| 87 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 88 | + d.SetInt(s) | |
| 89 | + if d.Int() != s { | |
| 90 | + err = strconv.ErrRange | |
| 91 | + d.SetInt(0) | |
| 92 | + } | |
| 93 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |
| 94 | + if s < 0 { | |
| 95 | + err = strconv.ErrRange | |
| 96 | + } else { | |
| 97 | + x := uint64(s) | |
| 98 | + d.SetUint(x) | |
| 99 | + if d.Uint() != x { | |
| 100 | + err = strconv.ErrRange | |
| 101 | + d.SetUint(0) | |
| 102 | + } | |
| 103 | + } | |
| 104 | + case reflect.Bool: | |
| 105 | + d.SetBool(s != 0) | |
| 106 | + default: | |
| 107 | + err = cannotConvert(d, s) | |
| 108 | + } | |
| 109 | + return | |
| 110 | +} | |
| 111 | + | |
| 112 | +func convertAssignValue(d reflect.Value, s interface{}) (err error) { | |
| 113 | + if d.Kind() != reflect.Ptr { | |
| 114 | + if d.CanAddr() { | |
| 115 | + d2 := d.Addr() | |
| 116 | + if d2.CanInterface() { | |
| 117 | + if scanner, ok := d2.Interface().(Scanner); ok { | |
| 118 | + return scanner.RedisScan(s) | |
| 119 | + } | |
| 120 | + } | |
| 121 | + } | |
| 122 | + } else if d.CanInterface() { | |
| 123 | + // Already a reflect.Ptr | |
| 124 | + if d.IsNil() { | |
| 125 | + d.Set(reflect.New(d.Type().Elem())) | |
| 126 | + } | |
| 127 | + if scanner, ok := d.Interface().(Scanner); ok { | |
| 128 | + return scanner.RedisScan(s) | |
| 129 | + } | |
| 130 | + } | |
| 131 | + | |
| 132 | + switch s := s.(type) { | |
| 133 | + case []byte: | |
| 134 | + err = convertAssignBulkString(d, s) | |
| 135 | + case int64: | |
| 136 | + err = convertAssignInt(d, s) | |
| 137 | + default: | |
| 138 | + err = cannotConvert(d, s) | |
| 139 | + } | |
| 140 | + return err | |
| 141 | +} | |
| 142 | + | |
| 143 | +func convertAssignArray(d reflect.Value, s []interface{}) error { | |
| 144 | + if d.Type().Kind() != reflect.Slice { | |
| 145 | + return cannotConvert(d, s) | |
| 146 | + } | |
| 147 | + ensureLen(d, len(s)) | |
| 148 | + for i := 0; i < len(s); i++ { | |
| 149 | + if err := convertAssignValue(d.Index(i), s[i]); err != nil { | |
| 150 | + return err | |
| 151 | + } | |
| 152 | + } | |
| 153 | + return nil | |
| 154 | +} | |
| 155 | + | |
| 156 | +func convertAssign(d interface{}, s interface{}) (err error) { | |
| 157 | + if scanner, ok := d.(Scanner); ok { | |
| 158 | + return scanner.RedisScan(s) | |
| 159 | + } | |
| 160 | + | |
| 161 | + // Handle the most common destination types using type switches and | |
| 162 | + // fall back to reflection for all other types. | |
| 163 | + switch s := s.(type) { | |
| 164 | + case nil: | |
| 165 | + // ignore | |
| 166 | + case []byte: | |
| 167 | + switch d := d.(type) { | |
| 168 | + case *string: | |
| 169 | + *d = string(s) | |
| 170 | + case *int: | |
| 171 | + *d, err = strconv.Atoi(string(s)) | |
| 172 | + case *bool: | |
| 173 | + *d, err = strconv.ParseBool(string(s)) | |
| 174 | + case *[]byte: | |
| 175 | + *d = s | |
| 176 | + case *interface{}: | |
| 177 | + *d = s | |
| 178 | + case nil: | |
| 179 | + // skip value | |
| 180 | + default: | |
| 181 | + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | |
| 182 | + err = cannotConvert(d, s) | |
| 183 | + } else { | |
| 184 | + err = convertAssignBulkString(d.Elem(), s) | |
| 185 | + } | |
| 186 | + } | |
| 187 | + case int64: | |
| 188 | + switch d := d.(type) { | |
| 189 | + case *int: | |
| 190 | + x := int(s) | |
| 191 | + if int64(x) != s { | |
| 192 | + err = strconv.ErrRange | |
| 193 | + x = 0 | |
| 194 | + } | |
| 195 | + *d = x | |
| 196 | + case *bool: | |
| 197 | + *d = s != 0 | |
| 198 | + case *interface{}: | |
| 199 | + *d = s | |
| 200 | + case nil: | |
| 201 | + // skip value | |
| 202 | + default: | |
| 203 | + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | |
| 204 | + err = cannotConvert(d, s) | |
| 205 | + } else { | |
| 206 | + err = convertAssignInt(d.Elem(), s) | |
| 207 | + } | |
| 208 | + } | |
| 209 | + case string: | |
| 210 | + switch d := d.(type) { | |
| 211 | + case *string: | |
| 212 | + *d = s | |
| 213 | + case *interface{}: | |
| 214 | + *d = s | |
| 215 | + case nil: | |
| 216 | + // skip value | |
| 217 | + default: | |
| 218 | + err = cannotConvert(reflect.ValueOf(d), s) | |
| 219 | + } | |
| 220 | + case []interface{}: | |
| 221 | + switch d := d.(type) { | |
| 222 | + case *[]interface{}: | |
| 223 | + *d = s | |
| 224 | + case *interface{}: | |
| 225 | + *d = s | |
| 226 | + case nil: | |
| 227 | + // skip value | |
| 228 | + default: | |
| 229 | + if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr { | |
| 230 | + err = cannotConvert(d, s) | |
| 231 | + } else { | |
| 232 | + err = convertAssignArray(d.Elem(), s) | |
| 233 | + } | |
| 234 | + } | |
| 235 | + case Error: | |
| 236 | + err = s | |
| 237 | + default: | |
| 238 | + err = cannotConvert(reflect.ValueOf(d), s) | |
| 239 | + } | |
| 240 | + return | |
| 241 | +} | |
| 242 | + | |
| 243 | +// Scan copies from src to the values pointed at by dest. | |
| 244 | +// | |
| 245 | +// Scan uses RedisScan if available otherwise: | |
| 246 | +// | |
| 247 | +// The values pointed at by dest must be an integer, float, boolean, string, | |
| 248 | +// []byte, interface{} or slices of these types. Scan uses the standard strconv | |
| 249 | +// package to convert bulk strings to numeric and boolean types. | |
| 250 | +// | |
| 251 | +// If a dest value is nil, then the corresponding src value is skipped. | |
| 252 | +// | |
| 253 | +// If a src element is nil, then the corresponding dest value is not modified. | |
| 254 | +// | |
| 255 | +// To enable easy use of Scan in a loop, Scan returns the slice of src | |
| 256 | +// following the copied values. | |
| 257 | +func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) { | |
| 258 | + if len(src) < len(dest) { | |
| 259 | + return nil, errors.New("redigo.Scan: array short") | |
| 260 | + } | |
| 261 | + var err error | |
| 262 | + for i, d := range dest { | |
| 263 | + err = convertAssign(d, src[i]) | |
| 264 | + if err != nil { | |
| 265 | + err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err) | |
| 266 | + break | |
| 267 | + } | |
| 268 | + } | |
| 269 | + return src[len(dest):], err | |
| 270 | +} | |
| 271 | + | |
| 272 | +type fieldSpec struct { | |
| 273 | + name string | |
| 274 | + index []int | |
| 275 | + omitEmpty bool | |
| 276 | +} | |
| 277 | + | |
| 278 | +type structSpec struct { | |
| 279 | + m map[string]*fieldSpec | |
| 280 | + l []*fieldSpec | |
| 281 | +} | |
| 282 | + | |
| 283 | +func (ss *structSpec) fieldSpec(name []byte) *fieldSpec { | |
| 284 | + return ss.m[string(name)] | |
| 285 | +} | |
| 286 | + | |
| 287 | +func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) { | |
| 288 | + for i := 0; i < t.NumField(); i++ { | |
| 289 | + f := t.Field(i) | |
| 290 | + switch { | |
| 291 | + case f.PkgPath != "" && !f.Anonymous: | |
| 292 | + // Ignore unexported fields. | |
| 293 | + case f.Anonymous: | |
| 294 | + // TODO: Handle pointers. Requires change to decoder and | |
| 295 | + // protection against infinite recursion. | |
| 296 | + if f.Type.Kind() == reflect.Struct { | |
| 297 | + compileStructSpec(f.Type, depth, append(index, i), ss) | |
| 298 | + } | |
| 299 | + default: | |
| 300 | + fs := &fieldSpec{name: f.Name} | |
| 301 | + tag := f.Tag.Get("redis") | |
| 302 | + p := strings.Split(tag, ",") | |
| 303 | + if len(p) > 0 { | |
| 304 | + if p[0] == "-" { | |
| 305 | + continue | |
| 306 | + } | |
| 307 | + if len(p[0]) > 0 { | |
| 308 | + fs.name = p[0] | |
| 309 | + } | |
| 310 | + for _, s := range p[1:] { | |
| 311 | + switch s { | |
| 312 | + case "omitempty": | |
| 313 | + fs.omitEmpty = true | |
| 314 | + default: | |
| 315 | + panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name())) | |
| 316 | + } | |
| 317 | + } | |
| 318 | + } | |
| 319 | + d, found := depth[fs.name] | |
| 320 | + if !found { | |
| 321 | + d = 1 << 30 | |
| 322 | + } | |
| 323 | + switch { | |
| 324 | + case len(index) == d: | |
| 325 | + // At same depth, remove from result. | |
| 326 | + delete(ss.m, fs.name) | |
| 327 | + j := 0 | |
| 328 | + for i := 0; i < len(ss.l); i++ { | |
| 329 | + if fs.name != ss.l[i].name { | |
| 330 | + ss.l[j] = ss.l[i] | |
| 331 | + j += 1 | |
| 332 | + } | |
| 333 | + } | |
| 334 | + ss.l = ss.l[:j] | |
| 335 | + case len(index) < d: | |
| 336 | + fs.index = make([]int, len(index)+1) | |
| 337 | + copy(fs.index, index) | |
| 338 | + fs.index[len(index)] = i | |
| 339 | + depth[fs.name] = len(index) | |
| 340 | + ss.m[fs.name] = fs | |
| 341 | + ss.l = append(ss.l, fs) | |
| 342 | + } | |
| 343 | + } | |
| 344 | + } | |
| 345 | +} | |
| 346 | + | |
| 347 | +var ( | |
| 348 | + structSpecMutex sync.RWMutex | |
| 349 | + structSpecCache = make(map[reflect.Type]*structSpec) | |
| 350 | + defaultFieldSpec = &fieldSpec{} | |
| 351 | +) | |
| 352 | + | |
| 353 | +func structSpecForType(t reflect.Type) *structSpec { | |
| 354 | + | |
| 355 | + structSpecMutex.RLock() | |
| 356 | + ss, found := structSpecCache[t] | |
| 357 | + structSpecMutex.RUnlock() | |
| 358 | + if found { | |
| 359 | + return ss | |
| 360 | + } | |
| 361 | + | |
| 362 | + structSpecMutex.Lock() | |
| 363 | + defer structSpecMutex.Unlock() | |
| 364 | + ss, found = structSpecCache[t] | |
| 365 | + if found { | |
| 366 | + return ss | |
| 367 | + } | |
| 368 | + | |
| 369 | + ss = &structSpec{m: make(map[string]*fieldSpec)} | |
| 370 | + compileStructSpec(t, make(map[string]int), nil, ss) | |
| 371 | + structSpecCache[t] = ss | |
| 372 | + return ss | |
| 373 | +} | |
| 374 | + | |
| 375 | +var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct") | |
| 376 | + | |
| 377 | +// ScanStruct scans alternating names and values from src to a struct. The | |
| 378 | +// HGETALL and CONFIG GET commands return replies in this format. | |
| 379 | +// | |
| 380 | +// ScanStruct uses exported field names to match values in the response. Use | |
| 381 | +// 'redis' field tag to override the name: | |
| 382 | +// | |
| 383 | +// Field int `redis:"myName"` | |
| 384 | +// | |
| 385 | +// Fields with the tag redis:"-" are ignored. | |
| 386 | +// | |
| 387 | +// Each field uses RedisScan if available otherwise: | |
| 388 | +// Integer, float, boolean, string and []byte fields are supported. Scan uses the | |
| 389 | +// standard strconv package to convert bulk string values to numeric and | |
| 390 | +// boolean types. | |
| 391 | +// | |
| 392 | +// If a src element is nil, then the corresponding field is not modified. | |
| 393 | +func ScanStruct(src []interface{}, dest interface{}) error { | |
| 394 | + d := reflect.ValueOf(dest) | |
| 395 | + if d.Kind() != reflect.Ptr || d.IsNil() { | |
| 396 | + return errScanStructValue | |
| 397 | + } | |
| 398 | + d = d.Elem() | |
| 399 | + if d.Kind() != reflect.Struct { | |
| 400 | + return errScanStructValue | |
| 401 | + } | |
| 402 | + ss := structSpecForType(d.Type()) | |
| 403 | + | |
| 404 | + if len(src)%2 != 0 { | |
| 405 | + return errors.New("redigo.ScanStruct: number of values not a multiple of 2") | |
| 406 | + } | |
| 407 | + | |
| 408 | + for i := 0; i < len(src); i += 2 { | |
| 409 | + s := src[i+1] | |
| 410 | + if s == nil { | |
| 411 | + continue | |
| 412 | + } | |
| 413 | + name, ok := src[i].([]byte) | |
| 414 | + if !ok { | |
| 415 | + return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i) | |
| 416 | + } | |
| 417 | + fs := ss.fieldSpec(name) | |
| 418 | + if fs == nil { | |
| 419 | + continue | |
| 420 | + } | |
| 421 | + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { | |
| 422 | + return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err) | |
| 423 | + } | |
| 424 | + } | |
| 425 | + return nil | |
| 426 | +} | |
| 427 | + | |
| 428 | +var ( | |
| 429 | + errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct") | |
| 430 | +) | |
| 431 | + | |
| 432 | +// ScanSlice scans src to the slice pointed to by dest. The elements the dest | |
| 433 | +// slice must be integer, float, boolean, string, struct or pointer to struct | |
| 434 | +// values. | |
| 435 | +// | |
| 436 | +// Struct fields must be integer, float, boolean or string values. All struct | |
| 437 | +// fields are used unless a subset is specified using fieldNames. | |
| 438 | +func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error { | |
| 439 | + d := reflect.ValueOf(dest) | |
| 440 | + if d.Kind() != reflect.Ptr || d.IsNil() { | |
| 441 | + return errScanSliceValue | |
| 442 | + } | |
| 443 | + d = d.Elem() | |
| 444 | + if d.Kind() != reflect.Slice { | |
| 445 | + return errScanSliceValue | |
| 446 | + } | |
| 447 | + | |
| 448 | + isPtr := false | |
| 449 | + t := d.Type().Elem() | |
| 450 | + if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct { | |
| 451 | + isPtr = true | |
| 452 | + t = t.Elem() | |
| 453 | + } | |
| 454 | + | |
| 455 | + if t.Kind() != reflect.Struct { | |
| 456 | + ensureLen(d, len(src)) | |
| 457 | + for i, s := range src { | |
| 458 | + if s == nil { | |
| 459 | + continue | |
| 460 | + } | |
| 461 | + if err := convertAssignValue(d.Index(i), s); err != nil { | |
| 462 | + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err) | |
| 463 | + } | |
| 464 | + } | |
| 465 | + return nil | |
| 466 | + } | |
| 467 | + | |
| 468 | + ss := structSpecForType(t) | |
| 469 | + fss := ss.l | |
| 470 | + if len(fieldNames) > 0 { | |
| 471 | + fss = make([]*fieldSpec, len(fieldNames)) | |
| 472 | + for i, name := range fieldNames { | |
| 473 | + fss[i] = ss.m[name] | |
| 474 | + if fss[i] == nil { | |
| 475 | + return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name) | |
| 476 | + } | |
| 477 | + } | |
| 478 | + } | |
| 479 | + | |
| 480 | + if len(fss) == 0 { | |
| 481 | + return errors.New("redigo.ScanSlice: no struct fields") | |
| 482 | + } | |
| 483 | + | |
| 484 | + n := len(src) / len(fss) | |
| 485 | + if n*len(fss) != len(src) { | |
| 486 | + return errors.New("redigo.ScanSlice: length not a multiple of struct field count") | |
| 487 | + } | |
| 488 | + | |
| 489 | + ensureLen(d, n) | |
| 490 | + for i := 0; i < n; i++ { | |
| 491 | + d := d.Index(i) | |
| 492 | + if isPtr { | |
| 493 | + if d.IsNil() { | |
| 494 | + d.Set(reflect.New(t)) | |
| 495 | + } | |
| 496 | + d = d.Elem() | |
| 497 | + } | |
| 498 | + for j, fs := range fss { | |
| 499 | + s := src[i*len(fss)+j] | |
| 500 | + if s == nil { | |
| 501 | + continue | |
| 502 | + } | |
| 503 | + if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil { | |
| 504 | + return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err) | |
| 505 | + } | |
| 506 | + } | |
| 507 | + } | |
| 508 | + return nil | |
| 509 | +} | |
| 510 | + | |
| 511 | +// Args is a helper for constructing command arguments from structured values. | |
| 512 | +type Args []interface{} | |
| 513 | + | |
| 514 | +// Add returns the result of appending value to args. | |
| 515 | +func (args Args) Add(value ...interface{}) Args { | |
| 516 | + return append(args, value...) | |
| 517 | +} | |
| 518 | + | |
| 519 | +// AddFlat returns the result of appending the flattened value of v to args. | |
| 520 | +// | |
| 521 | +// Maps are flattened by appending the alternating keys and map values to args. | |
| 522 | +// | |
| 523 | +// Slices are flattened by appending the slice elements to args. | |
| 524 | +// | |
| 525 | +// Structs are flattened by appending the alternating names and values of | |
| 526 | +// exported fields to args. If v is a nil struct pointer, then nothing is | |
| 527 | +// appended. The 'redis' field tag overrides struct field names. See ScanStruct | |
| 528 | +// for more information on the use of the 'redis' field tag. | |
| 529 | +// | |
| 530 | +// Other types are appended to args as is. | |
| 531 | +func (args Args) AddFlat(v interface{}) Args { | |
| 532 | + rv := reflect.ValueOf(v) | |
| 533 | + switch rv.Kind() { | |
| 534 | + case reflect.Struct: | |
| 535 | + args = flattenStruct(args, rv) | |
| 536 | + case reflect.Slice: | |
| 537 | + for i := 0; i < rv.Len(); i++ { | |
| 538 | + args = append(args, rv.Index(i).Interface()) | |
| 539 | + } | |
| 540 | + case reflect.Map: | |
| 541 | + for _, k := range rv.MapKeys() { | |
| 542 | + args = append(args, k.Interface(), rv.MapIndex(k).Interface()) | |
| 543 | + } | |
| 544 | + case reflect.Ptr: | |
| 545 | + if rv.Type().Elem().Kind() == reflect.Struct { | |
| 546 | + if !rv.IsNil() { | |
| 547 | + args = flattenStruct(args, rv.Elem()) | |
| 548 | + } | |
| 549 | + } else { | |
| 550 | + args = append(args, v) | |
| 551 | + } | |
| 552 | + default: | |
| 553 | + args = append(args, v) | |
| 554 | + } | |
| 555 | + return args | |
| 556 | +} | |
| 557 | + | |
| 558 | +func flattenStruct(args Args, v reflect.Value) Args { | |
| 559 | + ss := structSpecForType(v.Type()) | |
| 560 | + for _, fs := range ss.l { | |
| 561 | + fv := v.FieldByIndex(fs.index) | |
| 562 | + if fs.omitEmpty { | |
| 563 | + var empty = false | |
| 564 | + switch fv.Kind() { | |
| 565 | + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: | |
| 566 | + empty = fv.Len() == 0 | |
| 567 | + case reflect.Bool: | |
| 568 | + empty = !fv.Bool() | |
| 569 | + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |
| 570 | + empty = fv.Int() == 0 | |
| 571 | + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: | |
| 572 | + empty = fv.Uint() == 0 | |
| 573 | + case reflect.Float32, reflect.Float64: | |
| 574 | + empty = fv.Float() == 0 | |
| 575 | + case reflect.Interface, reflect.Ptr: | |
| 576 | + empty = fv.IsNil() | |
| 577 | + } | |
| 578 | + if empty { | |
| 579 | + continue | |
| 580 | + } | |
| 581 | + } | |
| 582 | + args = append(args, fs.name, fv.Interface()) | |
| 583 | + } | |
| 584 | + return args | |
| 585 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,494 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "fmt" | |
| 19 | + "math" | |
| 20 | + "reflect" | |
| 21 | + "testing" | |
| 22 | + "time" | |
| 23 | + | |
| 24 | + "github.com/garyburd/redigo/redis" | |
| 25 | +) | |
| 26 | + | |
| 27 | +type durationScan struct { | |
| 28 | + time.Duration `redis:"sd"` | |
| 29 | +} | |
| 30 | + | |
| 31 | +func (t *durationScan) RedisScan(src interface{}) (err error) { | |
| 32 | + if t == nil { | |
| 33 | + return fmt.Errorf("nil pointer") | |
| 34 | + } | |
| 35 | + switch src := src.(type) { | |
| 36 | + case string: | |
| 37 | + t.Duration, err = time.ParseDuration(src) | |
| 38 | + case []byte: | |
| 39 | + t.Duration, err = time.ParseDuration(string(src)) | |
| 40 | + case int64: | |
| 41 | + t.Duration = time.Duration(src) | |
| 42 | + default: | |
| 43 | + err = fmt.Errorf("cannot convert from %T to %T", src, t) | |
| 44 | + } | |
| 45 | + return err | |
| 46 | +} | |
| 47 | + | |
| 48 | +var scanConversionTests = []struct { | |
| 49 | + src interface{} | |
| 50 | + dest interface{} | |
| 51 | +}{ | |
| 52 | + {[]byte("-inf"), math.Inf(-1)}, | |
| 53 | + {[]byte("+inf"), math.Inf(1)}, | |
| 54 | + {[]byte("0"), float64(0)}, | |
| 55 | + {[]byte("3.14159"), float64(3.14159)}, | |
| 56 | + {[]byte("3.14"), float32(3.14)}, | |
| 57 | + {[]byte("-100"), int(-100)}, | |
| 58 | + {[]byte("101"), int(101)}, | |
| 59 | + {int64(102), int(102)}, | |
| 60 | + {[]byte("103"), uint(103)}, | |
| 61 | + {int64(104), uint(104)}, | |
| 62 | + {[]byte("105"), int8(105)}, | |
| 63 | + {int64(106), int8(106)}, | |
| 64 | + {[]byte("107"), uint8(107)}, | |
| 65 | + {int64(108), uint8(108)}, | |
| 66 | + {[]byte("0"), false}, | |
| 67 | + {int64(0), false}, | |
| 68 | + {[]byte("f"), false}, | |
| 69 | + {[]byte("1"), true}, | |
| 70 | + {int64(1), true}, | |
| 71 | + {[]byte("t"), true}, | |
| 72 | + {"hello", "hello"}, | |
| 73 | + {[]byte("hello"), "hello"}, | |
| 74 | + {[]byte("world"), []byte("world")}, | |
| 75 | + {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}}, | |
| 76 | + {[]interface{}{[]byte("foo")}, []string{"foo"}}, | |
| 77 | + {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}}, | |
| 78 | + {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}}, | |
| 79 | + {[]interface{}{[]byte("1")}, []int{1}}, | |
| 80 | + {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}}, | |
| 81 | + {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}}, | |
| 82 | + {[]interface{}{[]byte("1")}, []byte{1}}, | |
| 83 | + {[]interface{}{[]byte("1")}, []bool{true}}, | |
| 84 | + {"1m", durationScan{Duration: time.Minute}}, | |
| 85 | + {[]byte("1m"), durationScan{Duration: time.Minute}}, | |
| 86 | + {time.Minute.Nanoseconds(), durationScan{Duration: time.Minute}}, | |
| 87 | + {[]interface{}{[]byte("1m")}, []durationScan{durationScan{Duration: time.Minute}}}, | |
| 88 | + {[]interface{}{[]byte("1m")}, []*durationScan{&durationScan{Duration: time.Minute}}}, | |
| 89 | +} | |
| 90 | + | |
| 91 | +func TestScanConversion(t *testing.T) { | |
| 92 | + for _, tt := range scanConversionTests { | |
| 93 | + values := []interface{}{tt.src} | |
| 94 | + dest := reflect.New(reflect.TypeOf(tt.dest)) | |
| 95 | + values, err := redis.Scan(values, dest.Interface()) | |
| 96 | + if err != nil { | |
| 97 | + t.Errorf("Scan(%v) returned error %v", tt, err) | |
| 98 | + continue | |
| 99 | + } | |
| 100 | + if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) { | |
| 101 | + t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest) | |
| 102 | + } | |
| 103 | + } | |
| 104 | +} | |
| 105 | + | |
| 106 | +var scanConversionErrorTests = []struct { | |
| 107 | + src interface{} | |
| 108 | + dest interface{} | |
| 109 | +}{ | |
| 110 | + {[]byte("1234"), byte(0)}, | |
| 111 | + {int64(1234), byte(0)}, | |
| 112 | + {[]byte("-1"), byte(0)}, | |
| 113 | + {int64(-1), byte(0)}, | |
| 114 | + {[]byte("junk"), false}, | |
| 115 | + {redis.Error("blah"), false}, | |
| 116 | + {redis.Error("blah"), durationScan{Duration: time.Minute}}, | |
| 117 | + {"invalid", durationScan{Duration: time.Minute}}, | |
| 118 | +} | |
| 119 | + | |
| 120 | +func TestScanConversionError(t *testing.T) { | |
| 121 | + for _, tt := range scanConversionErrorTests { | |
| 122 | + values := []interface{}{tt.src} | |
| 123 | + dest := reflect.New(reflect.TypeOf(tt.dest)) | |
| 124 | + values, err := redis.Scan(values, dest.Interface()) | |
| 125 | + if err == nil { | |
| 126 | + t.Errorf("Scan(%v) did not return error", tt) | |
| 127 | + } | |
| 128 | + } | |
| 129 | +} | |
| 130 | + | |
| 131 | +func ExampleScan() { | |
| 132 | + c, err := dial() | |
| 133 | + if err != nil { | |
| 134 | + fmt.Println(err) | |
| 135 | + return | |
| 136 | + } | |
| 137 | + defer c.Close() | |
| 138 | + | |
| 139 | + c.Send("HMSET", "album:1", "title", "Red", "rating", 5) | |
| 140 | + c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) | |
| 141 | + c.Send("HMSET", "album:3", "title", "Beat") | |
| 142 | + c.Send("LPUSH", "albums", "1") | |
| 143 | + c.Send("LPUSH", "albums", "2") | |
| 144 | + c.Send("LPUSH", "albums", "3") | |
| 145 | + values, err := redis.Values(c.Do("SORT", "albums", | |
| 146 | + "BY", "album:*->rating", | |
| 147 | + "GET", "album:*->title", | |
| 148 | + "GET", "album:*->rating")) | |
| 149 | + if err != nil { | |
| 150 | + fmt.Println(err) | |
| 151 | + return | |
| 152 | + } | |
| 153 | + | |
| 154 | + for len(values) > 0 { | |
| 155 | + var title string | |
| 156 | + rating := -1 // initialize to illegal value to detect nil. | |
| 157 | + values, err = redis.Scan(values, &title, &rating) | |
| 158 | + if err != nil { | |
| 159 | + fmt.Println(err) | |
| 160 | + return | |
| 161 | + } | |
| 162 | + if rating == -1 { | |
| 163 | + fmt.Println(title, "not-rated") | |
| 164 | + } else { | |
| 165 | + fmt.Println(title, rating) | |
| 166 | + } | |
| 167 | + } | |
| 168 | + // Output: | |
| 169 | + // Beat not-rated | |
| 170 | + // Earthbound 1 | |
| 171 | + // Red 5 | |
| 172 | +} | |
| 173 | + | |
| 174 | +type s0 struct { | |
| 175 | + X int | |
| 176 | + Y int `redis:"y"` | |
| 177 | + Bt bool | |
| 178 | +} | |
| 179 | + | |
| 180 | +type s1 struct { | |
| 181 | + X int `redis:"-"` | |
| 182 | + I int `redis:"i"` | |
| 183 | + U uint `redis:"u"` | |
| 184 | + S string `redis:"s"` | |
| 185 | + P []byte `redis:"p"` | |
| 186 | + B bool `redis:"b"` | |
| 187 | + Bt bool | |
| 188 | + Bf bool | |
| 189 | + s0 | |
| 190 | + Sd durationScan `redis:"sd"` | |
| 191 | + Sdp *durationScan `redis:"sdp"` | |
| 192 | +} | |
| 193 | + | |
| 194 | +var scanStructTests = []struct { | |
| 195 | + title string | |
| 196 | + reply []string | |
| 197 | + value interface{} | |
| 198 | +}{ | |
| 199 | + {"basic", | |
| 200 | + []string{ | |
| 201 | + "i", "-1234", | |
| 202 | + "u", "5678", | |
| 203 | + "s", "hello", | |
| 204 | + "p", "world", | |
| 205 | + "b", "t", | |
| 206 | + "Bt", "1", | |
| 207 | + "Bf", "0", | |
| 208 | + "X", "123", | |
| 209 | + "y", "456", | |
| 210 | + "sd", "1m", | |
| 211 | + "sdp", "1m", | |
| 212 | + }, | |
| 213 | + &s1{ | |
| 214 | + I: -1234, | |
| 215 | + U: 5678, | |
| 216 | + S: "hello", | |
| 217 | + P: []byte("world"), | |
| 218 | + B: true, | |
| 219 | + Bt: true, | |
| 220 | + Bf: false, | |
| 221 | + s0: s0{X: 123, Y: 456}, | |
| 222 | + Sd: durationScan{Duration: time.Minute}, | |
| 223 | + Sdp: &durationScan{Duration: time.Minute}, | |
| 224 | + }, | |
| 225 | + }, | |
| 226 | +} | |
| 227 | + | |
| 228 | +func TestScanStruct(t *testing.T) { | |
| 229 | + for _, tt := range scanStructTests { | |
| 230 | + | |
| 231 | + var reply []interface{} | |
| 232 | + for _, v := range tt.reply { | |
| 233 | + reply = append(reply, []byte(v)) | |
| 234 | + } | |
| 235 | + | |
| 236 | + value := reflect.New(reflect.ValueOf(tt.value).Type().Elem()) | |
| 237 | + | |
| 238 | + if err := redis.ScanStruct(reply, value.Interface()); err != nil { | |
| 239 | + t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err) | |
| 240 | + } | |
| 241 | + | |
| 242 | + if !reflect.DeepEqual(value.Interface(), tt.value) { | |
| 243 | + t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value) | |
| 244 | + } | |
| 245 | + } | |
| 246 | +} | |
| 247 | + | |
| 248 | +func TestBadScanStructArgs(t *testing.T) { | |
| 249 | + x := []interface{}{"A", "b"} | |
| 250 | + test := func(v interface{}) { | |
| 251 | + if err := redis.ScanStruct(x, v); err == nil { | |
| 252 | + t.Errorf("Expect error for ScanStruct(%T, %T)", x, v) | |
| 253 | + } | |
| 254 | + } | |
| 255 | + | |
| 256 | + test(nil) | |
| 257 | + | |
| 258 | + var v0 *struct{} | |
| 259 | + test(v0) | |
| 260 | + | |
| 261 | + var v1 int | |
| 262 | + test(&v1) | |
| 263 | + | |
| 264 | + x = x[:1] | |
| 265 | + v2 := struct{ A string }{} | |
| 266 | + test(&v2) | |
| 267 | +} | |
| 268 | + | |
| 269 | +var scanSliceTests = []struct { | |
| 270 | + src []interface{} | |
| 271 | + fieldNames []string | |
| 272 | + ok bool | |
| 273 | + dest interface{} | |
| 274 | +}{ | |
| 275 | + { | |
| 276 | + []interface{}{[]byte("1"), nil, []byte("-1")}, | |
| 277 | + nil, | |
| 278 | + true, | |
| 279 | + []int{1, 0, -1}, | |
| 280 | + }, | |
| 281 | + { | |
| 282 | + []interface{}{[]byte("1"), nil, []byte("2")}, | |
| 283 | + nil, | |
| 284 | + true, | |
| 285 | + []uint{1, 0, 2}, | |
| 286 | + }, | |
| 287 | + { | |
| 288 | + []interface{}{[]byte("-1")}, | |
| 289 | + nil, | |
| 290 | + false, | |
| 291 | + []uint{1}, | |
| 292 | + }, | |
| 293 | + { | |
| 294 | + []interface{}{[]byte("hello"), nil, []byte("world")}, | |
| 295 | + nil, | |
| 296 | + true, | |
| 297 | + [][]byte{[]byte("hello"), nil, []byte("world")}, | |
| 298 | + }, | |
| 299 | + { | |
| 300 | + []interface{}{[]byte("hello"), nil, []byte("world")}, | |
| 301 | + nil, | |
| 302 | + true, | |
| 303 | + []string{"hello", "", "world"}, | |
| 304 | + }, | |
| 305 | + { | |
| 306 | + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | |
| 307 | + nil, | |
| 308 | + true, | |
| 309 | + []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, | |
| 310 | + }, | |
| 311 | + { | |
| 312 | + []interface{}{[]byte("a1"), []byte("b1")}, | |
| 313 | + nil, | |
| 314 | + false, | |
| 315 | + []struct{ A, B, C string }{{"a1", "b1", ""}}, | |
| 316 | + }, | |
| 317 | + { | |
| 318 | + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | |
| 319 | + nil, | |
| 320 | + true, | |
| 321 | + []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}}, | |
| 322 | + }, | |
| 323 | + { | |
| 324 | + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | |
| 325 | + []string{"A", "B"}, | |
| 326 | + true, | |
| 327 | + []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}}, | |
| 328 | + }, | |
| 329 | + { | |
| 330 | + []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")}, | |
| 331 | + nil, | |
| 332 | + false, | |
| 333 | + []struct{}{}, | |
| 334 | + }, | |
| 335 | +} | |
| 336 | + | |
| 337 | +func TestScanSlice(t *testing.T) { | |
| 338 | + for _, tt := range scanSliceTests { | |
| 339 | + | |
| 340 | + typ := reflect.ValueOf(tt.dest).Type() | |
| 341 | + dest := reflect.New(typ) | |
| 342 | + | |
| 343 | + err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...) | |
| 344 | + if tt.ok != (err == nil) { | |
| 345 | + t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err) | |
| 346 | + continue | |
| 347 | + } | |
| 348 | + if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) { | |
| 349 | + t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest) | |
| 350 | + } | |
| 351 | + } | |
| 352 | +} | |
| 353 | + | |
| 354 | +func ExampleScanSlice() { | |
| 355 | + c, err := dial() | |
| 356 | + if err != nil { | |
| 357 | + fmt.Println(err) | |
| 358 | + return | |
| 359 | + } | |
| 360 | + defer c.Close() | |
| 361 | + | |
| 362 | + c.Send("HMSET", "album:1", "title", "Red", "rating", 5) | |
| 363 | + c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1) | |
| 364 | + c.Send("HMSET", "album:3", "title", "Beat", "rating", 4) | |
| 365 | + c.Send("LPUSH", "albums", "1") | |
| 366 | + c.Send("LPUSH", "albums", "2") | |
| 367 | + c.Send("LPUSH", "albums", "3") | |
| 368 | + values, err := redis.Values(c.Do("SORT", "albums", | |
| 369 | + "BY", "album:*->rating", | |
| 370 | + "GET", "album:*->title", | |
| 371 | + "GET", "album:*->rating")) | |
| 372 | + if err != nil { | |
| 373 | + fmt.Println(err) | |
| 374 | + return | |
| 375 | + } | |
| 376 | + | |
| 377 | + var albums []struct { | |
| 378 | + Title string | |
| 379 | + Rating int | |
| 380 | + } | |
| 381 | + if err := redis.ScanSlice(values, &albums); err != nil { | |
| 382 | + fmt.Println(err) | |
| 383 | + return | |
| 384 | + } | |
| 385 | + fmt.Printf("%v\n", albums) | |
| 386 | + // Output: | |
| 387 | + // [{Earthbound 1} {Beat 4} {Red 5}] | |
| 388 | +} | |
| 389 | + | |
| 390 | +var argsTests = []struct { | |
| 391 | + title string | |
| 392 | + actual redis.Args | |
| 393 | + expected redis.Args | |
| 394 | +}{ | |
| 395 | + {"struct ptr", | |
| 396 | + redis.Args{}.AddFlat(&struct { | |
| 397 | + I int `redis:"i"` | |
| 398 | + U uint `redis:"u"` | |
| 399 | + S string `redis:"s"` | |
| 400 | + P []byte `redis:"p"` | |
| 401 | + M map[string]string `redis:"m"` | |
| 402 | + Bt bool | |
| 403 | + Bf bool | |
| 404 | + }{ | |
| 405 | + -1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false, | |
| 406 | + }), | |
| 407 | + redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false}, | |
| 408 | + }, | |
| 409 | + {"struct", | |
| 410 | + redis.Args{}.AddFlat(struct{ I int }{123}), | |
| 411 | + redis.Args{"I", 123}, | |
| 412 | + }, | |
| 413 | + {"slice", | |
| 414 | + redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2), | |
| 415 | + redis.Args{1, "a", "b", "c", 2}, | |
| 416 | + }, | |
| 417 | + {"struct omitempty", | |
| 418 | + redis.Args{}.AddFlat(&struct { | |
| 419 | + I int `redis:"i,omitempty"` | |
| 420 | + U uint `redis:"u,omitempty"` | |
| 421 | + S string `redis:"s,omitempty"` | |
| 422 | + P []byte `redis:"p,omitempty"` | |
| 423 | + M map[string]string `redis:"m,omitempty"` | |
| 424 | + Bt bool `redis:"Bt,omitempty"` | |
| 425 | + Bf bool `redis:"Bf,omitempty"` | |
| 426 | + }{ | |
| 427 | + 0, 0, "", []byte{}, map[string]string{}, true, false, | |
| 428 | + }), | |
| 429 | + redis.Args{"Bt", true}, | |
| 430 | + }, | |
| 431 | +} | |
| 432 | + | |
| 433 | +func TestArgs(t *testing.T) { | |
| 434 | + for _, tt := range argsTests { | |
| 435 | + if !reflect.DeepEqual(tt.actual, tt.expected) { | |
| 436 | + t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected) | |
| 437 | + } | |
| 438 | + } | |
| 439 | +} | |
| 440 | + | |
| 441 | +func ExampleArgs() { | |
| 442 | + c, err := dial() | |
| 443 | + if err != nil { | |
| 444 | + fmt.Println(err) | |
| 445 | + return | |
| 446 | + } | |
| 447 | + defer c.Close() | |
| 448 | + | |
| 449 | + var p1, p2 struct { | |
| 450 | + Title string `redis:"title"` | |
| 451 | + Author string `redis:"author"` | |
| 452 | + Body string `redis:"body"` | |
| 453 | + } | |
| 454 | + | |
| 455 | + p1.Title = "Example" | |
| 456 | + p1.Author = "Gary" | |
| 457 | + p1.Body = "Hello" | |
| 458 | + | |
| 459 | + if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil { | |
| 460 | + fmt.Println(err) | |
| 461 | + return | |
| 462 | + } | |
| 463 | + | |
| 464 | + m := map[string]string{ | |
| 465 | + "title": "Example2", | |
| 466 | + "author": "Steve", | |
| 467 | + "body": "Map", | |
| 468 | + } | |
| 469 | + | |
| 470 | + if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil { | |
| 471 | + fmt.Println(err) | |
| 472 | + return | |
| 473 | + } | |
| 474 | + | |
| 475 | + for _, id := range []string{"id1", "id2"} { | |
| 476 | + | |
| 477 | + v, err := redis.Values(c.Do("HGETALL", id)) | |
| 478 | + if err != nil { | |
| 479 | + fmt.Println(err) | |
| 480 | + return | |
| 481 | + } | |
| 482 | + | |
| 483 | + if err := redis.ScanStruct(v, &p2); err != nil { | |
| 484 | + fmt.Println(err) | |
| 485 | + return | |
| 486 | + } | |
| 487 | + | |
| 488 | + fmt.Printf("%+v\n", p2) | |
| 489 | + } | |
| 490 | + | |
| 491 | + // Output: | |
| 492 | + // {Title:Example Author:Gary Body:Hello} | |
| 493 | + // {Title:Example2 Author:Steve Body:Map} | |
| 494 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,91 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "crypto/sha1" | |
| 19 | + "encoding/hex" | |
| 20 | + "io" | |
| 21 | + "strings" | |
| 22 | +) | |
| 23 | + | |
| 24 | +// Script encapsulates the source, hash and key count for a Lua script. See | |
| 25 | +// http://redis.io/commands/eval for information on scripts in Redis. | |
| 26 | +type Script struct { | |
| 27 | + keyCount int | |
| 28 | + src string | |
| 29 | + hash string | |
| 30 | +} | |
| 31 | + | |
| 32 | +// NewScript returns a new script object. If keyCount is greater than or equal | |
| 33 | +// to zero, then the count is automatically inserted in the EVAL command | |
| 34 | +// argument list. If keyCount is less than zero, then the application supplies | |
| 35 | +// the count as the first value in the keysAndArgs argument to the Do, Send and | |
| 36 | +// SendHash methods. | |
| 37 | +func NewScript(keyCount int, src string) *Script { | |
| 38 | + h := sha1.New() | |
| 39 | + io.WriteString(h, src) | |
| 40 | + return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))} | |
| 41 | +} | |
| 42 | + | |
| 43 | +func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} { | |
| 44 | + var args []interface{} | |
| 45 | + if s.keyCount < 0 { | |
| 46 | + args = make([]interface{}, 1+len(keysAndArgs)) | |
| 47 | + args[0] = spec | |
| 48 | + copy(args[1:], keysAndArgs) | |
| 49 | + } else { | |
| 50 | + args = make([]interface{}, 2+len(keysAndArgs)) | |
| 51 | + args[0] = spec | |
| 52 | + args[1] = s.keyCount | |
| 53 | + copy(args[2:], keysAndArgs) | |
| 54 | + } | |
| 55 | + return args | |
| 56 | +} | |
| 57 | + | |
| 58 | +// Hash returns the script hash. | |
| 59 | +func (s *Script) Hash() string { | |
| 60 | + return s.hash | |
| 61 | +} | |
| 62 | + | |
| 63 | +// Do evaluates the script. Under the covers, Do optimistically evaluates the | |
| 64 | +// script using the EVALSHA command. If the command fails because the script is | |
| 65 | +// not loaded, then Do evaluates the script using the EVAL command (thus | |
| 66 | +// causing the script to load). | |
| 67 | +func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) { | |
| 68 | + v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...) | |
| 69 | + if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") { | |
| 70 | + v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...) | |
| 71 | + } | |
| 72 | + return v, err | |
| 73 | +} | |
| 74 | + | |
| 75 | +// SendHash evaluates the script without waiting for the reply. The script is | |
| 76 | +// evaluated with the EVALSHA command. The application must ensure that the | |
| 77 | +// script is loaded by a previous call to Send, Do or Load methods. | |
| 78 | +func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error { | |
| 79 | + return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...) | |
| 80 | +} | |
| 81 | + | |
| 82 | +// Send evaluates the script without waiting for the reply. | |
| 83 | +func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error { | |
| 84 | + return c.Send("EVAL", s.args(s.src, keysAndArgs)...) | |
| 85 | +} | |
| 86 | + | |
| 87 | +// Load loads the script without evaluating it. | |
| 88 | +func (s *Script) Load(c Conn) error { | |
| 89 | + _, err := c.Do("SCRIPT", "LOAD", s.src) | |
| 90 | + return err | |
| 91 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,100 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "fmt" | |
| 19 | + "reflect" | |
| 20 | + "testing" | |
| 21 | + "time" | |
| 22 | + | |
| 23 | + "github.com/garyburd/redigo/redis" | |
| 24 | +) | |
| 25 | + | |
| 26 | +var ( | |
| 27 | + // These variables are declared at package level to remove distracting | |
| 28 | + // details from the examples. | |
| 29 | + c redis.Conn | |
| 30 | + reply interface{} | |
| 31 | + err error | |
| 32 | +) | |
| 33 | + | |
| 34 | +func ExampleScript() { | |
| 35 | + // Initialize a package-level variable with a script. | |
| 36 | + var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`) | |
| 37 | + | |
| 38 | + // In a function, use the script Do method to evaluate the script. The Do | |
| 39 | + // method optimistically uses the EVALSHA command. If the script is not | |
| 40 | + // loaded, then the Do method falls back to the EVAL command. | |
| 41 | + reply, err = getScript.Do(c, "foo") | |
| 42 | +} | |
| 43 | + | |
| 44 | +func TestScript(t *testing.T) { | |
| 45 | + c, err := redis.DialDefaultServer() | |
| 46 | + if err != nil { | |
| 47 | + t.Fatalf("error connection to database, %v", err) | |
| 48 | + } | |
| 49 | + defer c.Close() | |
| 50 | + | |
| 51 | + // To test fall back in Do, we make script unique by adding comment with current time. | |
| 52 | + script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano()) | |
| 53 | + s := redis.NewScript(2, script) | |
| 54 | + reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")} | |
| 55 | + | |
| 56 | + v, err := s.Do(c, "key1", "key2", "arg1", "arg2") | |
| 57 | + if err != nil { | |
| 58 | + t.Errorf("s.Do(c, ...) returned %v", err) | |
| 59 | + } | |
| 60 | + | |
| 61 | + if !reflect.DeepEqual(v, reply) { | |
| 62 | + t.Errorf("s.Do(c, ..); = %v, want %v", v, reply) | |
| 63 | + } | |
| 64 | + | |
| 65 | + err = s.Load(c) | |
| 66 | + if err != nil { | |
| 67 | + t.Errorf("s.Load(c) returned %v", err) | |
| 68 | + } | |
| 69 | + | |
| 70 | + err = s.SendHash(c, "key1", "key2", "arg1", "arg2") | |
| 71 | + if err != nil { | |
| 72 | + t.Errorf("s.SendHash(c, ...) returned %v", err) | |
| 73 | + } | |
| 74 | + | |
| 75 | + err = c.Flush() | |
| 76 | + if err != nil { | |
| 77 | + t.Errorf("c.Flush() returned %v", err) | |
| 78 | + } | |
| 79 | + | |
| 80 | + v, err = c.Receive() | |
| 81 | + if !reflect.DeepEqual(v, reply) { | |
| 82 | + t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply) | |
| 83 | + } | |
| 84 | + | |
| 85 | + err = s.Send(c, "key1", "key2", "arg1", "arg2") | |
| 86 | + if err != nil { | |
| 87 | + t.Errorf("s.Send(c, ...) returned %v", err) | |
| 88 | + } | |
| 89 | + | |
| 90 | + err = c.Flush() | |
| 91 | + if err != nil { | |
| 92 | + t.Errorf("c.Flush() returned %v", err) | |
| 93 | + } | |
| 94 | + | |
| 95 | + v, err = c.Receive() | |
| 96 | + if !reflect.DeepEqual(v, reply) { | |
| 97 | + t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply) | |
| 98 | + } | |
| 99 | + | |
| 100 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,183 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "bufio" | |
| 19 | + "errors" | |
| 20 | + "flag" | |
| 21 | + "fmt" | |
| 22 | + "io" | |
| 23 | + "io/ioutil" | |
| 24 | + "os" | |
| 25 | + "os/exec" | |
| 26 | + "strconv" | |
| 27 | + "strings" | |
| 28 | + "sync" | |
| 29 | + "testing" | |
| 30 | + "time" | |
| 31 | +) | |
| 32 | + | |
| 33 | +func SetNowFunc(f func() time.Time) { | |
| 34 | + nowFunc = f | |
| 35 | +} | |
| 36 | + | |
| 37 | +var ( | |
| 38 | + ErrNegativeInt = errNegativeInt | |
| 39 | + | |
| 40 | + serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary") | |
| 41 | + serverAddress = flag.String("redis-address", "127.0.0.1", "The address of the server") | |
| 42 | + serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers") | |
| 43 | + serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`") | |
| 44 | + serverLog = ioutil.Discard | |
| 45 | + | |
| 46 | + defaultServerMu sync.Mutex | |
| 47 | + defaultServer *Server | |
| 48 | + defaultServerErr error | |
| 49 | +) | |
| 50 | + | |
| 51 | +type Server struct { | |
| 52 | + name string | |
| 53 | + cmd *exec.Cmd | |
| 54 | + done chan struct{} | |
| 55 | +} | |
| 56 | + | |
| 57 | +func NewServer(name string, args ...string) (*Server, error) { | |
| 58 | + s := &Server{ | |
| 59 | + name: name, | |
| 60 | + cmd: exec.Command(*serverPath, args...), | |
| 61 | + done: make(chan struct{}), | |
| 62 | + } | |
| 63 | + | |
| 64 | + r, err := s.cmd.StdoutPipe() | |
| 65 | + if err != nil { | |
| 66 | + return nil, err | |
| 67 | + } | |
| 68 | + | |
| 69 | + err = s.cmd.Start() | |
| 70 | + if err != nil { | |
| 71 | + return nil, err | |
| 72 | + } | |
| 73 | + | |
| 74 | + ready := make(chan error, 1) | |
| 75 | + go s.watch(r, ready) | |
| 76 | + | |
| 77 | + select { | |
| 78 | + case err = <-ready: | |
| 79 | + case <-time.After(time.Second * 10): | |
| 80 | + err = errors.New("timeout waiting for server to start") | |
| 81 | + } | |
| 82 | + | |
| 83 | + if err != nil { | |
| 84 | + s.Stop() | |
| 85 | + return nil, err | |
| 86 | + } | |
| 87 | + | |
| 88 | + return s, nil | |
| 89 | +} | |
| 90 | + | |
| 91 | +func (s *Server) watch(r io.Reader, ready chan error) { | |
| 92 | + fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name) | |
| 93 | + var listening bool | |
| 94 | + var text string | |
| 95 | + scn := bufio.NewScanner(r) | |
| 96 | + for scn.Scan() { | |
| 97 | + text = scn.Text() | |
| 98 | + fmt.Fprintf(serverLog, "%s\n", text) | |
| 99 | + if !listening { | |
| 100 | + if strings.Contains(text, " * Ready to accept connections") || | |
| 101 | + strings.Contains(text, " * The server is now ready to accept connections on port") { | |
| 102 | + listening = true | |
| 103 | + ready <- nil | |
| 104 | + } | |
| 105 | + } | |
| 106 | + } | |
| 107 | + if !listening { | |
| 108 | + ready <- fmt.Errorf("server exited: %s", text) | |
| 109 | + } | |
| 110 | + s.cmd.Wait() | |
| 111 | + fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name) | |
| 112 | + close(s.done) | |
| 113 | +} | |
| 114 | + | |
| 115 | +func (s *Server) Stop() { | |
| 116 | + s.cmd.Process.Signal(os.Interrupt) | |
| 117 | + <-s.done | |
| 118 | +} | |
| 119 | + | |
| 120 | +// stopDefaultServer stops the server created by DialDefaultServer. | |
| 121 | +func stopDefaultServer() { | |
| 122 | + defaultServerMu.Lock() | |
| 123 | + defer defaultServerMu.Unlock() | |
| 124 | + if defaultServer != nil { | |
| 125 | + defaultServer.Stop() | |
| 126 | + defaultServer = nil | |
| 127 | + } | |
| 128 | +} | |
| 129 | + | |
| 130 | +// DefaultServerAddr starts the test server if not already started and returns | |
| 131 | +// the address of that server. | |
| 132 | +func DefaultServerAddr() (string, error) { | |
| 133 | + defaultServerMu.Lock() | |
| 134 | + defer defaultServerMu.Unlock() | |
| 135 | + addr := fmt.Sprintf("%v:%d", *serverAddress, *serverBasePort) | |
| 136 | + if defaultServer != nil || defaultServerErr != nil { | |
| 137 | + return addr, defaultServerErr | |
| 138 | + } | |
| 139 | + defaultServer, defaultServerErr = NewServer( | |
| 140 | + "default", | |
| 141 | + "--port", strconv.Itoa(*serverBasePort), | |
| 142 | + "--bind", *serverAddress, | |
| 143 | + "--save", "", | |
| 144 | + "--appendonly", "no") | |
| 145 | + return addr, defaultServerErr | |
| 146 | +} | |
| 147 | + | |
| 148 | +// DialDefaultServer starts the test server if not already started and dials a | |
| 149 | +// connection to the server. | |
| 150 | +func DialDefaultServer() (Conn, error) { | |
| 151 | + addr, err := DefaultServerAddr() | |
| 152 | + if err != nil { | |
| 153 | + return nil, err | |
| 154 | + } | |
| 155 | + c, err := Dial("tcp", addr, DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second)) | |
| 156 | + if err != nil { | |
| 157 | + return nil, err | |
| 158 | + } | |
| 159 | + c.Do("FLUSHDB") | |
| 160 | + return c, nil | |
| 161 | +} | |
| 162 | + | |
| 163 | +func TestMain(m *testing.M) { | |
| 164 | + os.Exit(func() int { | |
| 165 | + flag.Parse() | |
| 166 | + | |
| 167 | + var f *os.File | |
| 168 | + if *serverLogName != "" { | |
| 169 | + var err error | |
| 170 | + f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600) | |
| 171 | + if err != nil { | |
| 172 | + fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err) | |
| 173 | + return 1 | |
| 174 | + } | |
| 175 | + defer f.Close() | |
| 176 | + serverLog = f | |
| 177 | + } | |
| 178 | + | |
| 179 | + defer stopDefaultServer() | |
| 180 | + | |
| 181 | + return m.Run() | |
| 182 | + }()) | |
| 183 | +} | ... | ... |
src/github.com/garyburd/redigo/redis/zpop_example_test.go
0 → 100644
| ... | ... | @@ -0,0 +1,114 @@ |
| 1 | +// Copyright 2013 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redis_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "fmt" | |
| 19 | + | |
| 20 | + "github.com/garyburd/redigo/redis" | |
| 21 | +) | |
| 22 | + | |
| 23 | +// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands. | |
| 24 | +func zpop(c redis.Conn, key string) (result string, err error) { | |
| 25 | + | |
| 26 | + defer func() { | |
| 27 | + // Return connection to normal state on error. | |
| 28 | + if err != nil { | |
| 29 | + c.Do("DISCARD") | |
| 30 | + } | |
| 31 | + }() | |
| 32 | + | |
| 33 | + // Loop until transaction is successful. | |
| 34 | + for { | |
| 35 | + if _, err := c.Do("WATCH", key); err != nil { | |
| 36 | + return "", err | |
| 37 | + } | |
| 38 | + | |
| 39 | + members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0)) | |
| 40 | + if err != nil { | |
| 41 | + return "", err | |
| 42 | + } | |
| 43 | + if len(members) != 1 { | |
| 44 | + return "", redis.ErrNil | |
| 45 | + } | |
| 46 | + | |
| 47 | + c.Send("MULTI") | |
| 48 | + c.Send("ZREM", key, members[0]) | |
| 49 | + queued, err := c.Do("EXEC") | |
| 50 | + if err != nil { | |
| 51 | + return "", err | |
| 52 | + } | |
| 53 | + | |
| 54 | + if queued != nil { | |
| 55 | + result = members[0] | |
| 56 | + break | |
| 57 | + } | |
| 58 | + } | |
| 59 | + | |
| 60 | + return result, nil | |
| 61 | +} | |
| 62 | + | |
| 63 | +// zpopScript pops a value from a ZSET. | |
| 64 | +var zpopScript = redis.NewScript(1, ` | |
| 65 | + local r = redis.call('ZRANGE', KEYS[1], 0, 0) | |
| 66 | + if r ~= nil then | |
| 67 | + r = r[1] | |
| 68 | + redis.call('ZREM', KEYS[1], r) | |
| 69 | + end | |
| 70 | + return r | |
| 71 | +`) | |
| 72 | + | |
| 73 | +// This example implements ZPOP as described at | |
| 74 | +// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting. | |
| 75 | +func Example_zpop() { | |
| 76 | + c, err := dial() | |
| 77 | + if err != nil { | |
| 78 | + fmt.Println(err) | |
| 79 | + return | |
| 80 | + } | |
| 81 | + defer c.Close() | |
| 82 | + | |
| 83 | + // Add test data using a pipeline. | |
| 84 | + | |
| 85 | + for i, member := range []string{"red", "blue", "green"} { | |
| 86 | + c.Send("ZADD", "zset", i, member) | |
| 87 | + } | |
| 88 | + if _, err := c.Do(""); err != nil { | |
| 89 | + fmt.Println(err) | |
| 90 | + return | |
| 91 | + } | |
| 92 | + | |
| 93 | + // Pop using WATCH/MULTI/EXEC | |
| 94 | + | |
| 95 | + v, err := zpop(c, "zset") | |
| 96 | + if err != nil { | |
| 97 | + fmt.Println(err) | |
| 98 | + return | |
| 99 | + } | |
| 100 | + fmt.Println(v) | |
| 101 | + | |
| 102 | + // Pop using a script. | |
| 103 | + | |
| 104 | + v, err = redis.String(zpopScript.Do(c, "zset")) | |
| 105 | + if err != nil { | |
| 106 | + fmt.Println(err) | |
| 107 | + return | |
| 108 | + } | |
| 109 | + fmt.Println(v) | |
| 110 | + | |
| 111 | + // Output: | |
| 112 | + // red | |
| 113 | + // blue | |
| 114 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,152 @@ |
| 1 | +// Copyright 2014 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redisx | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "errors" | |
| 19 | + "sync" | |
| 20 | + | |
| 21 | + "github.com/garyburd/redigo/internal" | |
| 22 | + "github.com/garyburd/redigo/redis" | |
| 23 | +) | |
| 24 | + | |
| 25 | +// ConnMux multiplexes one or more connections to a single underlying | |
| 26 | +// connection. The ConnMux connections do not support concurrency, commands | |
| 27 | +// that associate server side state with the connection or commands that put | |
| 28 | +// the connection in a special mode. | |
| 29 | +type ConnMux struct { | |
| 30 | + c redis.Conn | |
| 31 | + | |
| 32 | + sendMu sync.Mutex | |
| 33 | + sendID uint | |
| 34 | + | |
| 35 | + recvMu sync.Mutex | |
| 36 | + recvID uint | |
| 37 | + recvWait map[uint]chan struct{} | |
| 38 | +} | |
| 39 | + | |
| 40 | +func NewConnMux(c redis.Conn) *ConnMux { | |
| 41 | + return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})} | |
| 42 | +} | |
| 43 | + | |
| 44 | +// Get gets a connection. The application must close the returned connection. | |
| 45 | +func (p *ConnMux) Get() redis.Conn { | |
| 46 | + c := &muxConn{p: p} | |
| 47 | + c.ids = c.buf[:0] | |
| 48 | + return c | |
| 49 | +} | |
| 50 | + | |
| 51 | +// Close closes the underlying connection. | |
| 52 | +func (p *ConnMux) Close() error { | |
| 53 | + return p.c.Close() | |
| 54 | +} | |
| 55 | + | |
| 56 | +type muxConn struct { | |
| 57 | + p *ConnMux | |
| 58 | + ids []uint | |
| 59 | + buf [8]uint | |
| 60 | +} | |
| 61 | + | |
| 62 | +func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error { | |
| 63 | + if internal.LookupCommandInfo(cmd).Set != 0 { | |
| 64 | + return errors.New("command not supported by mux pool") | |
| 65 | + } | |
| 66 | + p := c.p | |
| 67 | + p.sendMu.Lock() | |
| 68 | + id := p.sendID | |
| 69 | + c.ids = append(c.ids, id) | |
| 70 | + p.sendID++ | |
| 71 | + err := p.c.Send(cmd, args...) | |
| 72 | + if flush { | |
| 73 | + err = p.c.Flush() | |
| 74 | + } | |
| 75 | + p.sendMu.Unlock() | |
| 76 | + return err | |
| 77 | +} | |
| 78 | + | |
| 79 | +func (c *muxConn) Send(cmd string, args ...interface{}) error { | |
| 80 | + return c.send(false, cmd, args...) | |
| 81 | +} | |
| 82 | + | |
| 83 | +func (c *muxConn) Flush() error { | |
| 84 | + p := c.p | |
| 85 | + p.sendMu.Lock() | |
| 86 | + err := p.c.Flush() | |
| 87 | + p.sendMu.Unlock() | |
| 88 | + return err | |
| 89 | +} | |
| 90 | + | |
| 91 | +func (c *muxConn) Receive() (interface{}, error) { | |
| 92 | + if len(c.ids) == 0 { | |
| 93 | + return nil, errors.New("mux pool underflow") | |
| 94 | + } | |
| 95 | + | |
| 96 | + id := c.ids[0] | |
| 97 | + c.ids = c.ids[1:] | |
| 98 | + if len(c.ids) == 0 { | |
| 99 | + c.ids = c.buf[:0] | |
| 100 | + } | |
| 101 | + | |
| 102 | + p := c.p | |
| 103 | + p.recvMu.Lock() | |
| 104 | + if p.recvID != id { | |
| 105 | + ch := make(chan struct{}) | |
| 106 | + p.recvWait[id] = ch | |
| 107 | + p.recvMu.Unlock() | |
| 108 | + <-ch | |
| 109 | + p.recvMu.Lock() | |
| 110 | + if p.recvID != id { | |
| 111 | + panic("out of sync") | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + v, err := p.c.Receive() | |
| 116 | + | |
| 117 | + id++ | |
| 118 | + p.recvID = id | |
| 119 | + ch, ok := p.recvWait[id] | |
| 120 | + if ok { | |
| 121 | + delete(p.recvWait, id) | |
| 122 | + } | |
| 123 | + p.recvMu.Unlock() | |
| 124 | + if ok { | |
| 125 | + ch <- struct{}{} | |
| 126 | + } | |
| 127 | + | |
| 128 | + return v, err | |
| 129 | +} | |
| 130 | + | |
| 131 | +func (c *muxConn) Close() error { | |
| 132 | + var err error | |
| 133 | + if len(c.ids) == 0 { | |
| 134 | + return nil | |
| 135 | + } | |
| 136 | + c.Flush() | |
| 137 | + for _ = range c.ids { | |
| 138 | + _, err = c.Receive() | |
| 139 | + } | |
| 140 | + return err | |
| 141 | +} | |
| 142 | + | |
| 143 | +func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) { | |
| 144 | + if err := c.send(true, cmd, args...); err != nil { | |
| 145 | + return nil, err | |
| 146 | + } | |
| 147 | + return c.Receive() | |
| 148 | +} | |
| 149 | + | |
| 150 | +func (c *muxConn) Err() error { | |
| 151 | + return c.p.c.Err() | |
| 152 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,259 @@ |
| 1 | +// Copyright 2014 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +package redisx_test | |
| 16 | + | |
| 17 | +import ( | |
| 18 | + "net/textproto" | |
| 19 | + "sync" | |
| 20 | + "testing" | |
| 21 | + | |
| 22 | + "github.com/garyburd/redigo/internal/redistest" | |
| 23 | + "github.com/garyburd/redigo/redis" | |
| 24 | + "github.com/garyburd/redigo/redisx" | |
| 25 | +) | |
| 26 | + | |
| 27 | +func TestConnMux(t *testing.T) { | |
| 28 | + c, err := redistest.Dial() | |
| 29 | + if err != nil { | |
| 30 | + t.Fatalf("error connection to database, %v", err) | |
| 31 | + } | |
| 32 | + m := redisx.NewConnMux(c) | |
| 33 | + defer m.Close() | |
| 34 | + | |
| 35 | + c1 := m.Get() | |
| 36 | + c2 := m.Get() | |
| 37 | + c1.Send("ECHO", "hello") | |
| 38 | + c2.Send("ECHO", "world") | |
| 39 | + c1.Flush() | |
| 40 | + c2.Flush() | |
| 41 | + s, err := redis.String(c1.Receive()) | |
| 42 | + if err != nil { | |
| 43 | + t.Fatal(err) | |
| 44 | + } | |
| 45 | + if s != "hello" { | |
| 46 | + t.Fatalf("echo returned %q, want %q", s, "hello") | |
| 47 | + } | |
| 48 | + s, err = redis.String(c2.Receive()) | |
| 49 | + if err != nil { | |
| 50 | + t.Fatal(err) | |
| 51 | + } | |
| 52 | + if s != "world" { | |
| 53 | + t.Fatalf("echo returned %q, want %q", s, "world") | |
| 54 | + } | |
| 55 | + c1.Close() | |
| 56 | + c2.Close() | |
| 57 | +} | |
| 58 | + | |
| 59 | +func TestConnMuxClose(t *testing.T) { | |
| 60 | + c, err := redistest.Dial() | |
| 61 | + if err != nil { | |
| 62 | + t.Fatalf("error connection to database, %v", err) | |
| 63 | + } | |
| 64 | + m := redisx.NewConnMux(c) | |
| 65 | + defer m.Close() | |
| 66 | + | |
| 67 | + c1 := m.Get() | |
| 68 | + c2 := m.Get() | |
| 69 | + | |
| 70 | + if err := c1.Send("ECHO", "hello"); err != nil { | |
| 71 | + t.Fatal(err) | |
| 72 | + } | |
| 73 | + if err := c1.Close(); err != nil { | |
| 74 | + t.Fatal(err) | |
| 75 | + } | |
| 76 | + | |
| 77 | + if err := c2.Send("ECHO", "world"); err != nil { | |
| 78 | + t.Fatal(err) | |
| 79 | + } | |
| 80 | + if err := c2.Flush(); err != nil { | |
| 81 | + t.Fatal(err) | |
| 82 | + } | |
| 83 | + | |
| 84 | + s, err := redis.String(c2.Receive()) | |
| 85 | + if err != nil { | |
| 86 | + t.Fatal(err) | |
| 87 | + } | |
| 88 | + if s != "world" { | |
| 89 | + t.Fatalf("echo returned %q, want %q", s, "world") | |
| 90 | + } | |
| 91 | + c2.Close() | |
| 92 | +} | |
| 93 | + | |
| 94 | +func BenchmarkConn(b *testing.B) { | |
| 95 | + b.StopTimer() | |
| 96 | + c, err := redistest.Dial() | |
| 97 | + if err != nil { | |
| 98 | + b.Fatalf("error connection to database, %v", err) | |
| 99 | + } | |
| 100 | + defer c.Close() | |
| 101 | + b.StartTimer() | |
| 102 | + | |
| 103 | + for i := 0; i < b.N; i++ { | |
| 104 | + if _, err := c.Do("PING"); err != nil { | |
| 105 | + b.Fatal(err) | |
| 106 | + } | |
| 107 | + } | |
| 108 | +} | |
| 109 | + | |
| 110 | +func BenchmarkConnMux(b *testing.B) { | |
| 111 | + b.StopTimer() | |
| 112 | + c, err := redistest.Dial() | |
| 113 | + if err != nil { | |
| 114 | + b.Fatalf("error connection to database, %v", err) | |
| 115 | + } | |
| 116 | + m := redisx.NewConnMux(c) | |
| 117 | + defer m.Close() | |
| 118 | + | |
| 119 | + b.StartTimer() | |
| 120 | + | |
| 121 | + for i := 0; i < b.N; i++ { | |
| 122 | + c := m.Get() | |
| 123 | + if _, err := c.Do("PING"); err != nil { | |
| 124 | + b.Fatal(err) | |
| 125 | + } | |
| 126 | + c.Close() | |
| 127 | + } | |
| 128 | +} | |
| 129 | + | |
| 130 | +func BenchmarkPool(b *testing.B) { | |
| 131 | + b.StopTimer() | |
| 132 | + | |
| 133 | + p := redis.Pool{Dial: redistest.Dial, MaxIdle: 1} | |
| 134 | + defer p.Close() | |
| 135 | + | |
| 136 | + // Fill the pool. | |
| 137 | + c := p.Get() | |
| 138 | + if err := c.Err(); err != nil { | |
| 139 | + b.Fatal(err) | |
| 140 | + } | |
| 141 | + c.Close() | |
| 142 | + | |
| 143 | + b.StartTimer() | |
| 144 | + | |
| 145 | + for i := 0; i < b.N; i++ { | |
| 146 | + c := p.Get() | |
| 147 | + if _, err := c.Do("PING"); err != nil { | |
| 148 | + b.Fatal(err) | |
| 149 | + } | |
| 150 | + c.Close() | |
| 151 | + } | |
| 152 | +} | |
| 153 | + | |
| 154 | +const numConcurrent = 10 | |
| 155 | + | |
| 156 | +func BenchmarkConnMuxConcurrent(b *testing.B) { | |
| 157 | + b.StopTimer() | |
| 158 | + c, err := redistest.Dial() | |
| 159 | + if err != nil { | |
| 160 | + b.Fatalf("error connection to database, %v", err) | |
| 161 | + } | |
| 162 | + defer c.Close() | |
| 163 | + | |
| 164 | + m := redisx.NewConnMux(c) | |
| 165 | + | |
| 166 | + var wg sync.WaitGroup | |
| 167 | + wg.Add(numConcurrent) | |
| 168 | + | |
| 169 | + b.StartTimer() | |
| 170 | + | |
| 171 | + for i := 0; i < numConcurrent; i++ { | |
| 172 | + go func() { | |
| 173 | + defer wg.Done() | |
| 174 | + for i := 0; i < b.N; i++ { | |
| 175 | + c := m.Get() | |
| 176 | + if _, err := c.Do("PING"); err != nil { | |
| 177 | + b.Fatal(err) | |
| 178 | + } | |
| 179 | + c.Close() | |
| 180 | + } | |
| 181 | + }() | |
| 182 | + } | |
| 183 | + wg.Wait() | |
| 184 | +} | |
| 185 | + | |
| 186 | +func BenchmarkPoolConcurrent(b *testing.B) { | |
| 187 | + b.StopTimer() | |
| 188 | + | |
| 189 | + p := redis.Pool{Dial: redistest.Dial, MaxIdle: numConcurrent} | |
| 190 | + defer p.Close() | |
| 191 | + | |
| 192 | + // Fill the pool. | |
| 193 | + conns := make([]redis.Conn, numConcurrent) | |
| 194 | + for i := range conns { | |
| 195 | + c := p.Get() | |
| 196 | + if err := c.Err(); err != nil { | |
| 197 | + b.Fatal(err) | |
| 198 | + } | |
| 199 | + conns[i] = c | |
| 200 | + } | |
| 201 | + for _, c := range conns { | |
| 202 | + c.Close() | |
| 203 | + } | |
| 204 | + | |
| 205 | + var wg sync.WaitGroup | |
| 206 | + wg.Add(numConcurrent) | |
| 207 | + | |
| 208 | + b.StartTimer() | |
| 209 | + | |
| 210 | + for i := 0; i < numConcurrent; i++ { | |
| 211 | + go func() { | |
| 212 | + defer wg.Done() | |
| 213 | + for i := 0; i < b.N; i++ { | |
| 214 | + c := p.Get() | |
| 215 | + if _, err := c.Do("PING"); err != nil { | |
| 216 | + b.Fatal(err) | |
| 217 | + } | |
| 218 | + c.Close() | |
| 219 | + } | |
| 220 | + }() | |
| 221 | + } | |
| 222 | + wg.Wait() | |
| 223 | +} | |
| 224 | + | |
| 225 | +func BenchmarkPipelineConcurrency(b *testing.B) { | |
| 226 | + b.StopTimer() | |
| 227 | + c, err := redistest.Dial() | |
| 228 | + if err != nil { | |
| 229 | + b.Fatalf("error connection to database, %v", err) | |
| 230 | + } | |
| 231 | + defer c.Close() | |
| 232 | + | |
| 233 | + var wg sync.WaitGroup | |
| 234 | + wg.Add(numConcurrent) | |
| 235 | + | |
| 236 | + var pipeline textproto.Pipeline | |
| 237 | + | |
| 238 | + b.StartTimer() | |
| 239 | + | |
| 240 | + for i := 0; i < numConcurrent; i++ { | |
| 241 | + go func() { | |
| 242 | + defer wg.Done() | |
| 243 | + for i := 0; i < b.N; i++ { | |
| 244 | + id := pipeline.Next() | |
| 245 | + pipeline.StartRequest(id) | |
| 246 | + c.Send("PING") | |
| 247 | + c.Flush() | |
| 248 | + pipeline.EndRequest(id) | |
| 249 | + pipeline.StartResponse(id) | |
| 250 | + _, err := c.Receive() | |
| 251 | + if err != nil { | |
| 252 | + b.Fatal(err) | |
| 253 | + } | |
| 254 | + pipeline.EndResponse(id) | |
| 255 | + } | |
| 256 | + }() | |
| 257 | + } | |
| 258 | + wg.Wait() | |
| 259 | +} | ... | ... |
| ... | ... | @@ -0,0 +1,17 @@ |
| 1 | +// Copyright 2012 Gary Burd | |
| 2 | +// | |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"): you may | |
| 4 | +// not use this file except in compliance with the License. You may obtain | |
| 5 | +// a copy of the License at | |
| 6 | +// | |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | +// | |
| 9 | +// Unless required by applicable law or agreed to in writing, software | |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | |
| 11 | +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | |
| 12 | +// License for the specific language governing permissions and limitations | |
| 13 | +// under the License. | |
| 14 | + | |
| 15 | +// Package redisx contains experimental features for Redigo. Features in this | |
| 16 | +// package may be modified or deleted at any time. | |
| 17 | +package redisx // import "github.com/garyburd/redigo/redisx" | ... | ... |