Commit 629a330b82a5c6942942f24dceed6741690c3b7b

Authored by 陆恒
1 parent 0aff5f86
Exists in master

first commit

Showing 46 changed files with 8351 additions and 0 deletions   Show diff stats
.gitignore 0 → 100644
... ... @@ -0,0 +1,2 @@
  1 +*.txt
  2 +.idea/
0 3 \ No newline at end of file
... ...
src/HttpServer/conf/conf.go 0 → 100644
... ... @@ -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 +}
... ...
src/HttpServer/conf/world.xml 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<Config>
  3 + <ServerHttpAddr Host="127.0.0.1:50056" />
  4 + <Redis Host="172.21.0.25:6379" Db="6" Password="crs-lzslccdc:redis@YXp8Jk#MV" />
  5 +</Config>
0 6 \ No newline at end of file
... ...
src/HttpServer/logic/datadef.go 0 → 100644
... ... @@ -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 +)
... ...
src/HttpServer/logic/httpserver.go 0 → 100644
... ... @@ -0,0 +1,106 @@
  1 +package logic
  2 +
  3 +import (
  4 + "HttpServer/conf"
  5 + "bytes"
  6 + "common/logger"
  7 + "encoding/json"
  8 + "strconv"
  9 +
  10 + "io/ioutil"
  11 + //"log"
  12 + "net/http"
  13 +)
  14 +
  15 +//定时处理倒计时
  16 +func StartHttpTicker() {
  17 +
  18 +}
  19 +
  20 +func StartHttpServe() {
  21 + startServerHttpServe()
  22 +}
  23 +
  24 +//just for test
  25 +func Testsendhttp() {
  26 + var test UserLoginData
  27 + //test.Uuid = 100
  28 + test.Fromid = 200
  29 + test.Sharetype = 1
  30 +
  31 + client := &http.Client{}
  32 +
  33 + bys, err := json.Marshal(&test)
  34 + if err != nil {
  35 + logger.Error("testsendhttp failed=%v", err)
  36 + return
  37 + }
  38 + body := bytes.NewBuffer(bys)
  39 + url := "http://127.0.0.1:50056/cat/login"
  40 + reqest, err := http.NewRequest("POST", url, body)
  41 + if err != nil {
  42 + logger.Error("http.NewRequest failed")
  43 + }
  44 + reqest.Header.Add("Uuid", "101")
  45 + //发送
  46 + //res, err := http.Post(url, "application/json;charset=utf-8", body)
  47 + res, err := client.Do(reqest)
  48 + if err != nil {
  49 + logger.Error(" post failed to %v err:%v data:%v", url, err, string(bys))
  50 + return
  51 + }
  52 +
  53 + result, _ := ioutil.ReadAll(res.Body)
  54 + res.Body.Close()
  55 +
  56 + s := string(result)
  57 + var resp GetUserDataResp
  58 + resp.Code = 0
  59 + var rdata UserLoginResp
  60 + _ = json.Unmarshal([]byte(s), &rdata)
  61 + logger.Info("testsendhttp , body:%v", rdata)
  62 +}
  63 +
  64 +func CheckErr(err error) {
  65 + if err != nil {
  66 + panic(err)
  67 + }
  68 +}
  69 +
  70 +func startServerHttpServe() {
  71 + http.HandleFunc("/cat/login", UserLogin) //登录
  72 +
  73 +
  74 + err := http.ListenAndServe(conf.GetServerHttpAddrConf(), nil)
  75 + CheckErr(err)
  76 +}
  77 +
  78 +
  79 +
  80 +func UserLogin(w http.ResponseWriter, r *http.Request) {
  81 + //logger.Info("%%%%%%%%%%%%%%%%path=%v", *r.URL)
  82 + //for k, v := range r.Header {
  83 + // logger.Info("*********************key=%v,value=%v", k, v)
  84 + //}
  85 +
  86 + //w.Header().Add("Access-Control-Allow-Headers", "")
  87 + //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")
  88 +
  89 + Uuid := 0
  90 + if len(r.Header) > 0 {
  91 + Uuid, _ = strconv.Atoi(r.Header.Get("Uuid"))
  92 + }
  93 +
  94 + if Uuid == 0 {
  95 + return
  96 + }
  97 + result, _ := ioutil.ReadAll(r.Body)
  98 + r.Body.Close()
  99 +
  100 + s := string(result)
  101 + logger.Info("UserLogin , body:%v,uuid=%v", s, Uuid)
  102 +
  103 + //HandlerLogin(w, s, Uuid)
  104 +}
  105 +
  106 +
... ...
src/HttpServer/logic/logic.go 0 → 100644
... ... @@ -0,0 +1 @@
  1 +package logic
0 2 \ No newline at end of file
... ...
src/HttpServer/main/main.go 0 → 100644
... ... @@ -0,0 +1,76 @@
  1 +package main
  2 +
  3 +import (
  4 + "HttpServer/conf"
  5 +
  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 +
  49 +
  50 + err = redishandler.Init()
  51 + if err != nil {
  52 + logger.Info("err init redis err=%v", err)
  53 + return
  54 + }
  55 +
  56 + go logic.StartHttpServe()
  57 + go logic.StartHttpTicker()
  58 + time.Sleep(time.Duration(2) * time.Second)
  59 + logic.Testsendhttp()
  60 +
  61 + select {
  62 + case _ = <-ch:
  63 + logger.Info("---I'm done----")
  64 + break
  65 + }
  66 +}
  67 +
  68 +func waitFor(ch chan int, secs int) {
  69 + if secs <= 0 {
  70 + return
  71 + }
  72 + time.Sleep(time.Duration(secs) * time.Second)
  73 +
  74 + logger.Info("-------------ready to exit--------------------")
  75 + ch <- 1
  76 +}
... ...
src/HttpServer/redishandler/redishandler.go 0 → 100644
... ... @@ -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 +}
... ...
src/common/beegomap/beegomap.go 0 → 100644
... ... @@ -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 +}
... ...
src/common/logger/logger.go 0 → 100644
... ... @@ -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 +}
... ...
src/common/redis/def.go 0 → 100644
... ... @@ -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 +)
... ...
src/common/redis/pubsub.go 0 → 100644
... ... @@ -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 +}
... ...
src/common/redis/redis.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd.zip 0 → 100644
No preview for this file type
src/github.com/garyburd/redigo/.github/CONTRIBUTING.md 0 → 100644
... ... @@ -0,0 +1,5 @@
  1 +Ask questions at
  2 +[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).
  3 +
  4 +[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your
  5 +plans before doing any work on Redigo.
... ...
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
... ...
src/github.com/garyburd/redigo/.travis.yml 0 → 100644
... ... @@ -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 ./...
... ...
src/github.com/garyburd/redigo/LICENSE 0 → 100644
... ... @@ -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.
... ...
src/github.com/garyburd/redigo/README.markdown 0 → 100644
... ... @@ -0,0 +1,51 @@
  1 +Redigo
  2 +======
  3 +
  4 +[![Build Status](https://travis-ci.org/garyburd/redigo.svg?branch=master)](https://travis-ci.org/garyburd/redigo)
  5 +[![GoDoc](https://godoc.org/github.com/garyburd/redigo/redis?status.svg)](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).
... ...
src/github.com/garyburd/redigo/internal/commandinfo.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/conn.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/conn_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/doc.go 0 → 100644
... ... @@ -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"
... ...
src/github.com/garyburd/redigo/redis/go17.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/log.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/pool.go 0 → 100644
... ... @@ -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 }
... ...
src/github.com/garyburd/redigo/redis/pool_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/pre_go17.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/pubsub.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/pubsub_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/redis.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/reply.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/reply_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/scan.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/scan_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/script.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/script_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redis/test_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redisx/connmux.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redisx/connmux_test.go 0 → 100644
... ... @@ -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 +}
... ...
src/github.com/garyburd/redigo/redisx/doc.go 0 → 100644
... ... @@ -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"
... ...