github.com/cellofellow/gopkg@v0.0.0-20140722061823-eec0544a62ad/database/redis/client.go (about) 1 // Copyright 2014 <chaishushan{AT}gmail.com>. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package redis 6 7 import ( 8 "bufio" 9 "bytes" 10 "errors" 11 "net" 12 "strconv" 13 "time" 14 ) 15 16 const ( 17 bufSize int = 4096 18 ) 19 20 // Common errors 21 var ( 22 AuthError error = errors.New("authentication failed") 23 LoadingError error = errors.New("server is busy loading dataset in memory") 24 ParseError error = errors.New("parse error") 25 PipelineQueueEmptyError error = errors.New("pipeline queue empty") 26 ) 27 28 //* Client 29 30 // Client describes a Redis client. 31 type Client struct { 32 conn net.Conn 33 timeout time.Duration 34 reader *bufio.Reader 35 pending []*request 36 completed []*Reply 37 } 38 39 // Dial connects to the given Redis server with the given timeout. 40 func DialTimeout(network, addr string, timeout time.Duration) (*Client, error) { 41 // establish a connection 42 conn, err := net.Dial(network, addr) 43 if err != nil { 44 return nil, err 45 } 46 47 c := new(Client) 48 c.conn = conn 49 c.timeout = timeout 50 c.reader = bufio.NewReaderSize(conn, bufSize) 51 return c, nil 52 } 53 54 // Dial connects to the given Redis server. 55 func Dial(network, addr string) (*Client, error) { 56 return DialTimeout(network, addr, time.Duration(0)) 57 } 58 59 //* Public methods 60 61 // Close closes the connection. 62 func (c *Client) Close() error { 63 return c.conn.Close() 64 } 65 66 // Cmd calls the given Redis command. 67 func (c *Client) Cmd(cmd string, args ...interface{}) *Reply { 68 err := c.writeRequest(&request{cmd, args}) 69 if err != nil { 70 return &Reply{Type: ErrorReply, Err: err} 71 } 72 return c.readReply() 73 } 74 75 // Append adds the given call to the pipeline queue. 76 // Use GetReply() to read the reply. 77 func (c *Client) Append(cmd string, args ...interface{}) { 78 c.pending = append(c.pending, &request{cmd, args}) 79 } 80 81 // GetReply returns the reply for the next request in the pipeline queue. 82 // Error reply with PipelineQueueEmptyError is returned, 83 // if the pipeline queue is empty. 84 func (c *Client) GetReply() *Reply { 85 if len(c.completed) > 0 { 86 r := c.completed[0] 87 c.completed = c.completed[1:] 88 return r 89 } 90 c.completed = nil 91 92 if len(c.pending) == 0 { 93 return &Reply{Type: ErrorReply, Err: PipelineQueueEmptyError} 94 } 95 96 nreqs := len(c.pending) 97 err := c.writeRequest(c.pending...) 98 c.pending = nil 99 if err != nil { 100 return &Reply{Type: ErrorReply, Err: err} 101 } 102 r := c.readReply() 103 c.completed = make([]*Reply, nreqs-1) 104 for i := 0; i < nreqs-1; i++ { 105 c.completed[i] = c.readReply() 106 } 107 108 return r 109 } 110 111 //* Private methods 112 113 func (c *Client) setReadTimeout() { 114 if c.timeout != 0 { 115 c.conn.SetReadDeadline(time.Now().Add(c.timeout)) 116 } 117 } 118 119 func (c *Client) setWriteTimeout() { 120 if c.timeout != 0 { 121 c.conn.SetWriteDeadline(time.Now().Add(c.timeout)) 122 } 123 } 124 125 func (c *Client) readReply() *Reply { 126 c.setReadTimeout() 127 return c.parse() 128 } 129 130 func (c *Client) writeRequest(requests ...*request) error { 131 c.setWriteTimeout() 132 _, err := c.conn.Write(createRequest(requests...)) 133 if err != nil { 134 c.Close() 135 return err 136 } 137 return nil 138 } 139 140 func (c *Client) parse() (r *Reply) { 141 r = new(Reply) 142 b, err := c.reader.ReadBytes('\n') 143 if err != nil { 144 c.Close() 145 r.Type = ErrorReply 146 r.Err = err 147 return 148 } 149 150 fb := b[0] 151 b = b[1 : len(b)-2] // get rid of the first byte and the trailing \r\n 152 switch fb { 153 case '-': 154 // error reply 155 r.Type = ErrorReply 156 if bytes.HasPrefix(b, []byte("LOADING")) { 157 r.Err = LoadingError 158 } else { 159 r.Err = errors.New(string(b)) 160 } 161 case '+': 162 // status reply 163 r.Type = StatusReply 164 r.buf = b 165 case ':': 166 // integer reply 167 i, err := strconv.ParseInt(string(b), 10, 64) 168 if err != nil { 169 r.Type = ErrorReply 170 r.Err = ParseError 171 } else { 172 r.Type = IntegerReply 173 r.int = i 174 } 175 case '$': 176 // bulk reply 177 i, err := strconv.Atoi(string(b)) 178 if err != nil { 179 r.Type = ErrorReply 180 r.Err = ParseError 181 } else { 182 if i == -1 { 183 // null bulk reply (key not found) 184 r.Type = NilReply 185 } else { 186 // bulk reply 187 ir := i + 2 188 br := make([]byte, ir) 189 rc := 0 190 191 for rc < ir { 192 n, err := c.reader.Read(br[rc:]) 193 if err != nil { 194 c.Close() 195 r.Type = ErrorReply 196 r.Err = err 197 } 198 rc += n 199 } 200 r.Type = BulkReply 201 r.buf = br[0:i] 202 } 203 } 204 case '*': 205 // multi bulk reply 206 i, err := strconv.Atoi(string(b)) 207 if err != nil { 208 r.Type = ErrorReply 209 r.Err = ParseError 210 } else { 211 switch { 212 case i == -1: 213 // null multi bulk 214 r.Type = NilReply 215 case i >= 0: 216 // multi bulk 217 // parse the replies recursively 218 r.Type = MultiReply 219 r.Elems = make([]*Reply, i) 220 for i := range r.Elems { 221 r.Elems[i] = c.parse() 222 } 223 default: 224 // invalid multi bulk reply 225 r.Type = ErrorReply 226 r.Err = ParseError 227 } 228 } 229 default: 230 // invalid reply 231 r.Type = ErrorReply 232 r.Err = ParseError 233 } 234 return 235 }