ledisdb/ssdb/client.go

214 lines
3.6 KiB
Go

package ssdb
import (
"bufio"
"errors"
"github.com/siddontang/golib/hack"
"github.com/siddontang/golib/log"
"io"
"net"
"runtime"
"strconv"
"strings"
)
var errReadRequest = errors.New("read request error, invalid format")
type client struct {
app *App
c net.Conn
rb *bufio.Reader
wb *bufio.Writer
cmd string
args [][]byte
}
func newClient(c net.Conn, app *App) {
co := new(client)
co.app = app
co.c = c
co.rb = bufio.NewReaderSize(c, 256)
co.wb = bufio.NewWriterSize(c, 256)
go co.run()
}
func (c *client) run() {
defer func() {
if e := recover(); e != nil {
buf := make([]byte, 4096)
n := runtime.Stack(buf, false)
buf = buf[0:n]
log.Fatal("client run panic %s:%v", buf, e)
}
c.c.Close()
}()
for {
req, err := c.readRequest()
if err != nil {
log.Info("read request error %v", err)
return
}
c.handleRequest(req)
}
}
func (c *client) readLine() ([]byte, error) {
var line []byte
for {
l, more, err := c.rb.ReadLine()
if err != nil {
return nil, err
}
if line == nil && !more {
return l, nil
}
line = append(line, l...)
if !more {
break
}
}
return line, nil
}
//A client sends to the Redis server a RESP Array consisting of just Bulk Strings.
func (c *client) readRequest() ([][]byte, error) {
l, err := c.readLine()
if err != nil {
return nil, err
} else if len(l) == 0 || l[0] != '*' {
return nil, errReadRequest
}
var n int
if n, err = strconv.Atoi(hack.String(l[1:])); err != nil {
return nil, err
} else if n <= 0 {
return nil, errReadRequest
}
req := make([][]byte, 0, n)
for i := 0; i < n; i++ {
if l, err = c.readLine(); err != nil {
return nil, err
}
if len(l) == 0 {
return nil, errReadRequest
} else if l[0] == '$' {
//handle resp string
if n, err = strconv.Atoi(hack.String(l[1:])); err != nil {
return nil, err
} else if n == -1 {
req = append(req, nil)
} else {
buf := make([]byte, n+2)
if _, err = io.ReadFull(c.rb, buf); err != nil {
return nil, err
} else if buf[len(buf)-2] != '\r' || buf[len(buf)-1] != '\n' {
return nil, errReadRequest
} else {
req = append(req, buf[0:len(buf)-2])
}
}
} else {
return nil, errReadRequest
}
}
return req, nil
}
func (c *client) handleRequest(req [][]byte) {
var err error
if len(req) == 0 {
err = ErrEmptyCommand
} else {
c.cmd = strings.ToLower(hack.String(req[0]))
c.args = req[1:]
f, ok := regCmds[c.cmd]
if !ok {
err = ErrNotFound
} else {
err = f(c)
}
}
if err != nil {
c.writeError(err)
}
c.wb.Flush()
}
func (c *client) writeError(err error) {
c.wb.Write(hack.Slice("-ERR"))
if err != nil {
c.wb.WriteByte(' ')
c.wb.Write(hack.Slice(err.Error()))
}
c.wb.Write(Delims)
}
func (c *client) writeStatus(status string) {
c.wb.WriteByte('+')
c.wb.Write(hack.Slice(status))
c.wb.Write(Delims)
}
func (c *client) writeInteger(n int64) {
c.wb.WriteByte(':')
c.wb.Write(hack.Slice(strconv.FormatInt(n, 10)))
c.wb.Write(Delims)
}
func (c *client) writeBulk(b []byte) {
c.wb.WriteByte('$')
if b == nil {
c.wb.Write(NullBulk)
} else {
c.wb.Write(hack.Slice(strconv.Itoa(len(b))))
c.wb.Write(Delims)
c.wb.Write(b)
}
c.wb.Write(Delims)
}
func (c *client) writeArray(ay []interface{}) {
c.wb.WriteByte('*')
if ay == nil {
c.wb.Write(NullArray)
c.wb.Write(Delims)
} else {
c.wb.Write(hack.Slice(strconv.Itoa(len(ay))))
c.wb.Write(Delims)
for i := 0; i < len(ay); i++ {
switch v := ay[i].(type) {
case []interface{}:
c.writeArray(v)
case []byte:
c.writeBulk(v)
case nil:
c.writeBulk(nil)
case int64:
c.writeInteger(v)
default:
panic("invalid array type")
}
}
}
}