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  }